diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_LmhnaWdub3Jl --- /dev/null +++ b/.hgignore @@ -0,0 +1,40 @@ +syntax: glob + +*.pyd* +*.pyc +*~ +*temp +*.html + +\#*\# + +doc/_build/* +doc/**generated/* +doc/html.zip +doc/ipynb/*_files/*.png +doc/ipynb/*.rst + +*.ipynb_checkpoints* +*Untitled*.ipynb + +*.egg-info/* + +build/* +dist/*.tar.gz +dist* + +*.so + +*fftw2Dmpi_cylib.c +*fftw2Dmpi_ccylib.c +*.o + +*setofvariables.c +*operators.c +*cyfunc.c + +*cy.c + +scripts\Experiments\path_working_exp.txt + +scripts/Simul/Launch/Profile.prof \ No newline at end of file diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Q0hBTkdFUy5yc3Q= --- /dev/null +++ b/CHANGES.rst @@ -0,0 +1,7 @@ + +0.0.8a +------ + +- The fluiddyn package now only contains base files for the FluidDyn + project. Other packages (fluidsim, fluidlab, ...) provide other + files. diff --git a/HOWTO.txt b/HOWTO.txt new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_SE9XVE8udHh0 --- /dev/null +++ b/HOWTO.txt @@ -0,0 +1,11 @@ +How to +====== + +How to upload to PyPI — the Python Package Index +------------------------------------------------ + +First, run the tests:: + python -m unittest discover + +With a correct $HOME/.pypirc, run:: + python setup.py sdist upload diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_TElDRU5TRS50eHQ= --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,518 @@ + + CeCILL FREE SOFTWARE LICENSE AGREEMENT + +Version 2.1 dated 2013-06-21 + + + Notice + +This Agreement is a Free Software license agreement that is the result +of discussions between its authors in order to ensure compliance with +the two main principles guiding its drafting: + + * firstly, compliance with the principles governing the distribution + of Free Software: access to source code, broad rights granted to users, + * secondly, the election of a governing law, French law, with which it + is conformant, both as regards the law of torts and intellectual + property law, and the protection that it offers to both authors and + holders of the economic rights over software. + +The authors of the CeCILL (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) +license are: + +Commissariat à l'énergie atomique et aux énergies alternatives - CEA, a +public scientific, technical and industrial research establishment, +having its principal place of business at 25 rue Leblanc, immeuble Le +Ponant D, 75015 Paris, France. + +Centre National de la Recherche Scientifique - CNRS, a public scientific +and technological establishment, having its principal place of business +at 3 rue Michel-Ange, 75794 Paris cedex 16, France. + +Institut National de Recherche en Informatique et en Automatique - +Inria, a public scientific and technological establishment, having its +principal place of business at Domaine de Voluceau, Rocquencourt, BP +105, 78153 Le Chesnay cedex, France. + + + Preamble + +The purpose of this Free Software license agreement is to grant users +the right to modify and redistribute the software governed by this +license within the framework of an open source distribution model. + +The exercising of this right is conditional upon certain obligations for +users so as to preserve this status for all subsequent redistributions. + +In consideration of access to the source code and the rights to copy, +modify and redistribute granted by the license, users are provided only +with a limited warranty and the software's author, the holder of the +economic rights, and the successive licensors only have limited liability. + +In this respect, the risks associated with loading, using, modifying +and/or developing or reproducing the software by the user are brought to +the user's attention, given its Free Software status, which may make it +complicated to use, with the result that its use is reserved for +developers and experienced professionals having in-depth computer +knowledge. Users are therefore encouraged to load and test the +suitability of the software as regards their requirements in conditions +enabling the security of their systems and/or data to be ensured and, +more generally, to use and operate it in the same conditions of +security. This Agreement may be freely reproduced and published, +provided it is not altered, and that no provisions are either added or +removed herefrom. + +This Agreement may apply to any or all software for which the holder of +the economic rights decides to submit the use thereof to its provisions. + +Frequently asked questions can be found on the official website of the +CeCILL licenses family (http://www.cecill.info/index.en.html) for any +necessary clarification. + + + Article 1 - DEFINITIONS + +For the purpose of this Agreement, when the following expressions +commence with a capital letter, they shall have the following meaning: + +Agreement: means this license agreement, and its possible subsequent +versions and annexes. + +Software: means the software in its Object Code and/or Source Code form +and, where applicable, its documentation, "as is" when the Licensee +accepts the Agreement. + +Initial Software: means the Software in its Source Code and possibly its +Object Code form and, where applicable, its documentation, "as is" when +it is first distributed under the terms and conditions of the Agreement. + +Modified Software: means the Software modified by at least one +Contribution. + +Source Code: means all the Software's instructions and program lines to +which access is required so as to modify the Software. + +Object Code: means the binary files originating from the compilation of +the Source Code. + +Holder: means the holder(s) of the economic rights over the Initial +Software. + +Licensee: means the Software user(s) having accepted the Agreement. + +Contributor: means a Licensee having made at least one Contribution. + +Licensor: means the Holder, or any other individual or legal entity, who +distributes the Software under the Agreement. + +Contribution: means any or all modifications, corrections, translations, +adaptations and/or new functions integrated into the Software by any or +all Contributors, as well as any or all Internal Modules. + +Module: means a set of sources files including their documentation that +enables supplementary functions or services in addition to those offered +by the Software. + +External Module: means any or all Modules, not derived from the +Software, so that this Module and the Software run in separate address +spaces, with one calling the other when they are run. + +Internal Module: means any or all Module, connected to the Software so +that they both execute in the same address space. + +GNU GPL: means the GNU General Public License version 2 or any +subsequent version, as published by the Free Software Foundation Inc. + +GNU Affero GPL: means the GNU Affero General Public License version 3 or +any subsequent version, as published by the Free Software Foundation Inc. + +EUPL: means the European Union Public License version 1.1 or any +subsequent version, as published by the European Commission. + +Parties: mean both the Licensee and the Licensor. + +These expressions may be used both in singular and plural form. + + + Article 2 - PURPOSE + +The purpose of the Agreement is the grant by the Licensor to the +Licensee of a non-exclusive, transferable and worldwide license for the +Software as set forth in Article 5 <#scope> hereinafter for the whole +term of the protection granted by the rights over said Software. + + + Article 3 - ACCEPTANCE + +3.1 The Licensee shall be deemed as having accepted the terms and +conditions of this Agreement upon the occurrence of the first of the +following events: + + * (i) loading the Software by any or all means, notably, by + downloading from a remote server, or by loading from a physical medium; + * (ii) the first time the Licensee exercises any of the rights granted + hereunder. + +3.2 One copy of the Agreement, containing a notice relating to the +characteristics of the Software, to the limited warranty, and to the +fact that its use is restricted to experienced users has been provided +to the Licensee prior to its acceptance as set forth in Article 3.1 +<#accepting> hereinabove, and the Licensee hereby acknowledges that it +has read and understood it. + + + Article 4 - EFFECTIVE DATE AND TERM + + + 4.1 EFFECTIVE DATE + +The Agreement shall become effective on the date when it is accepted by +the Licensee as set forth in Article 3.1 <#accepting>. + + + 4.2 TERM + +The Agreement shall remain in force for the entire legal term of +protection of the economic rights over the Software. + + + Article 5 - SCOPE OF RIGHTS GRANTED + +The Licensor hereby grants to the Licensee, who accepts, the following +rights over the Software for any or all use, and for the term of the +Agreement, on the basis of the terms and conditions set forth hereinafter. + +Besides, if the Licensor owns or comes to own one or more patents +protecting all or part of the functions of the Software or of its +components, the Licensor undertakes not to enforce the rights granted by +these patents against successive Licensees using, exploiting or +modifying the Software. If these patents are transferred, the Licensor +undertakes to have the transferees subscribe to the obligations set +forth in this paragraph. + + + 5.1 RIGHT OF USE + +The Licensee is authorized to use the Software, without any limitation +as to its fields of application, with it being hereinafter specified +that this comprises: + + 1. permanent or temporary reproduction of all or part of the Software + by any or all means and in any or all form. + + 2. loading, displaying, running, or storing the Software on any or all + medium. + + 3. entitlement to observe, study or test its operation so as to + determine the ideas and principles behind any or all constituent + elements of said Software. This shall apply when the Licensee + carries out any or all loading, displaying, running, transmission or + storage operation as regards the Software, that it is entitled to + carry out hereunder. + + + 5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS + +The right to make Contributions includes the right to translate, adapt, +arrange, or make any or all modifications to the Software, and the right +to reproduce the resulting software. + +The Licensee is authorized to make any or all Contributions to the +Software provided that it includes an explicit notice that it is the +author of said Contribution and indicates the date of the creation thereof. + + + 5.3 RIGHT OF DISTRIBUTION + +In particular, the right of distribution includes the right to publish, +transmit and communicate the Software to the general public on any or +all medium, and by any or all means, and the right to market, either in +consideration of a fee, or free of charge, one or more copies of the +Software by any means. + +The Licensee is further authorized to distribute copies of the modified +or unmodified Software to third parties according to the terms and +conditions set forth hereinafter. + + + 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION + +The Licensee is authorized to distribute true copies of the Software in +Source Code or Object Code form, provided that said distribution +complies with all the provisions of the Agreement and is accompanied by: + + 1. a copy of the Agreement, + + 2. a notice relating to the limitation of both the Licensor's warranty + and liability as set forth in Articles 8 and 9, + +and that, in the event that only the Object Code of the Software is +redistributed, the Licensee allows effective access to the full Source +Code of the Software for a period of at least three years from the +distribution of the Software, it being understood that the additional +acquisition cost of the Source Code shall not exceed the cost of the +data transfer. + + + 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE + +When the Licensee makes a Contribution to the Software, the terms and +conditions for the distribution of the resulting Modified Software +become subject to all the provisions of this Agreement. + +The Licensee is authorized to distribute the Modified Software, in +source code or object code form, provided that said distribution +complies with all the provisions of the Agreement and is accompanied by: + + 1. a copy of the Agreement, + + 2. a notice relating to the limitation of both the Licensor's warranty + and liability as set forth in Articles 8 and 9, + +and, in the event that only the object code of the Modified Software is +redistributed, + + 3. a note stating the conditions of effective access to the full source + code of the Modified Software for a period of at least three years + from the distribution of the Modified Software, it being understood + that the additional acquisition cost of the source code shall not + exceed the cost of the data transfer. + + + 5.3.3 DISTRIBUTION OF EXTERNAL MODULES + +When the Licensee has developed an External Module, the terms and +conditions of this Agreement do not apply to said External Module, that +may be distributed under a separate license agreement. + + + 5.3.4 COMPATIBILITY WITH OTHER LICENSES + +The Licensee can include a code that is subject to the provisions of one +of the versions of the GNU GPL, GNU Affero GPL and/or EUPL in the +Modified or unmodified Software, and distribute that entire code under +the terms of the same version of the GNU GPL, GNU Affero GPL and/or EUPL. + +The Licensee can include the Modified or unmodified Software in a code +that is subject to the provisions of one of the versions of the GNU GPL, +GNU Affero GPL and/or EUPL and distribute that entire code under the +terms of the same version of the GNU GPL, GNU Affero GPL and/or EUPL. + + + Article 6 - INTELLECTUAL PROPERTY + + + 6.1 OVER THE INITIAL SOFTWARE + +The Holder owns the economic rights over the Initial Software. Any or +all use of the Initial Software is subject to compliance with the terms +and conditions under which the Holder has elected to distribute its work +and no one shall be entitled to modify the terms and conditions for the +distribution of said Initial Software. + +The Holder undertakes that the Initial Software will remain ruled at +least by this Agreement, for the duration set forth in Article 4.2 <#term>. + + + 6.2 OVER THE CONTRIBUTIONS + +The Licensee who develops a Contribution is the owner of the +intellectual property rights over this Contribution as defined by +applicable law. + + + 6.3 OVER THE EXTERNAL MODULES + +The Licensee who develops an External Module is the owner of the +intellectual property rights over this External Module as defined by +applicable law and is free to choose the type of agreement that shall +govern its distribution. + + + 6.4 JOINT PROVISIONS + +The Licensee expressly undertakes: + + 1. not to remove, or modify, in any manner, the intellectual property + notices attached to the Software; + + 2. to reproduce said notices, in an identical manner, in the copies of + the Software modified or not. + +The Licensee undertakes not to directly or indirectly infringe the +intellectual property rights on the Software of the Holder and/or +Contributors, and to take, where applicable, vis-à-vis its staff, any +and all measures required to ensure respect of said intellectual +property rights of the Holder and/or Contributors. + + + Article 7 - RELATED SERVICES + +7.1 Under no circumstances shall the Agreement oblige the Licensor to +provide technical assistance or maintenance services for the Software. + +However, the Licensor is entitled to offer this type of services. The +terms and conditions of such technical assistance, and/or such +maintenance, shall be set forth in a separate instrument. Only the +Licensor offering said maintenance and/or technical assistance services +shall incur liability therefor. + +7.2 Similarly, any Licensor is entitled to offer to its licensees, under +its sole responsibility, a warranty, that shall only be binding upon +itself, for the redistribution of the Software and/or the Modified +Software, under terms and conditions that it is free to decide. Said +warranty, and the financial terms and conditions of its application, +shall be subject of a separate instrument executed between the Licensor +and the Licensee. + + + Article 8 - LIABILITY + +8.1 Subject to the provisions of Article 8.2, the Licensee shall be +entitled to claim compensation for any direct loss it may have suffered +from the Software as a result of a fault on the part of the relevant +Licensor, subject to providing evidence thereof. + +8.2 The Licensor's liability is limited to the commitments made under +this Agreement and shall not be incurred as a result of in particular: +(i) loss due the Licensee's total or partial failure to fulfill its +obligations, (ii) direct or consequential loss that is suffered by the +Licensee due to the use or performance of the Software, and (iii) more +generally, any consequential loss. In particular the Parties expressly +agree that any or all pecuniary or business loss (i.e. loss of data, +loss of profits, operating loss, loss of customers or orders, +opportunity cost, any disturbance to business activities) or any or all +legal proceedings instituted against the Licensee by a third party, +shall constitute consequential loss and shall not provide entitlement to +any or all compensation from the Licensor. + + + Article 9 - WARRANTY + +9.1 The Licensee acknowledges that the scientific and technical +state-of-the-art when the Software was distributed did not enable all +possible uses to be tested and verified, nor for the presence of +possible defects to be detected. In this respect, the Licensee's +attention has been drawn to the risks associated with loading, using, +modifying and/or developing and reproducing the Software which are +reserved for experienced users. + +The Licensee shall be responsible for verifying, by any or all means, +the suitability of the product for its requirements, its good working +order, and for ensuring that it shall not cause damage to either persons +or properties. + +9.2 The Licensor hereby represents, in good faith, that it is entitled +to grant all the rights over the Software (including in particular the +rights set forth in Article 5 <#scope>). + +9.3 The Licensee acknowledges that the Software is supplied "as is" by +the Licensor without any other express or tacit warranty, other than +that provided for in Article 9.2 <#good-faith> and, in particular, +without any warranty as to its commercial value, its secured, safe, +innovative or relevant nature. + +Specifically, the Licensor does not warrant that the Software is free +from any error, that it will operate without interruption, that it will +be compatible with the Licensee's own equipment and software +configuration, nor that it will meet the Licensee's requirements. + +9.4 The Licensor does not either expressly or tacitly warrant that the +Software does not infringe any third party intellectual property right +relating to a patent, software or any other property right. Therefore, +the Licensor disclaims any and all liability towards the Licensee +arising out of any or all proceedings for infringement that may be +instituted in respect of the use, modification and redistribution of the +Software. Nevertheless, should such proceedings be instituted against +the Licensee, the Licensor shall provide it with technical and legal +expertise for its defense. Such technical and legal expertise shall be +decided on a case-by-case basis between the relevant Licensor and the +Licensee pursuant to a memorandum of understanding. The Licensor +disclaims any and all liability as regards the Licensee's use of the +name of the Software. No warranty is given as regards the existence of +prior rights over the name of the Software or as regards the existence +of a trademark. + + + Article 10 - TERMINATION + +10.1 In the event of a breach by the Licensee of its obligations +hereunder, the Licensor may automatically terminate this Agreement +thirty (30) days after notice has been sent to the Licensee and has +remained ineffective. + +10.2 A Licensee whose Agreement is terminated shall no longer be +authorized to use, modify or distribute the Software. However, any +licenses that it may have granted prior to termination of the Agreement +shall remain valid subject to their having been granted in compliance +with the terms and conditions hereof. + + + Article 11 - MISCELLANEOUS + + + 11.1 EXCUSABLE EVENTS + +Neither Party shall be liable for any or all delay, or failure to +perform the Agreement, that may be attributable to an event of force +majeure, an act of God or an outside cause, such as defective +functioning or interruptions of the electricity or telecommunications +networks, network paralysis following a virus attack, intervention by +government authorities, natural disasters, water damage, earthquakes, +fire, explosions, strikes and labor unrest, war, etc. + +11.2 Any failure by either Party, on one or more occasions, to invoke +one or more of the provisions hereof, shall under no circumstances be +interpreted as being a waiver by the interested Party of its right to +invoke said provision(s) subsequently. + +11.3 The Agreement cancels and replaces any or all previous agreements, +whether written or oral, between the Parties and having the same +purpose, and constitutes the entirety of the agreement between said +Parties concerning said purpose. No supplement or modification to the +terms and conditions hereof shall be effective as between the Parties +unless it is made in writing and signed by their duly authorized +representatives. + +11.4 In the event that one or more of the provisions hereof were to +conflict with a current or future applicable act or legislative text, +said act or legislative text shall prevail, and the Parties shall make +the necessary amendments so as to comply with said act or legislative +text. All other provisions shall remain effective. Similarly, invalidity +of a provision of the Agreement, for any reason whatsoever, shall not +cause the Agreement as a whole to be invalid. + + + 11.5 LANGUAGE + +The Agreement is drafted in both French and English and both versions +are deemed authentic. + + + Article 12 - NEW VERSIONS OF THE AGREEMENT + +12.1 Any person is authorized to duplicate and distribute copies of this +Agreement. + +12.2 So as to ensure coherence, the wording of this Agreement is +protected and may only be modified by the authors of the License, who +reserve the right to periodically publish updates or new versions of the +Agreement, each with a separate number. These subsequent versions may +address new issues encountered by Free Software. + +12.3 Any Software distributed under a given version of the Agreement may +only be subsequently distributed under the same version of the Agreement +or a subsequent version, subject to the provisions of Article 5.3.4 +<#compatibility>. + + + Article 13 - GOVERNING LAW AND JURISDICTION + +13.1 The Agreement is governed by French law. The Parties agree to +endeavor to seek an amicable solution to any disagreements or disputes +that may arise during the performance of the Agreement. + +13.2 Failing an amicable solution within two (2) months as from their +occurrence, and unless emergency proceedings are necessary, the +disagreements or disputes shall be referred to the Paris Courts having +jurisdiction, by the more diligent Party. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_TUFOSUZFU1QuaW4= --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include *.txt +include *.rst +recursive-include doc *.rst +recursive-include fluidsim *.h +recursive-include fluidsim *.pyx +recursive-include fluidsim *.pxd +recursive-include fluidsim *.c +recursive-include include *.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_TWFrZWZpbGU= --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ + +clean_so: + find fluidsim -name "*.so" -delete + +tests: + python -m unittest discover diff --git a/README.rst b/README.rst new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_UkVBRE1FLnJzdA== --- /dev/null +++ b/README.rst @@ -0,0 +1,52 @@ +======== +FluidSim +======== + +*Framework for studying fluid dynamics by numerical simulation.* + +`Package Documentation <http://pythonhosted.org/fluidsim>`__ + +FluidSim is the numerically oriented package of the `FluidDyn project +<http://pythonhosted.org/fluiddyn>`__. The project is still in a +testing stage so it is still pretty unstable and many of its planned +features have not yet been implemented. + +It has first been developed by `Pierre Augier +<http://www.legi.grenoble-inp.fr/people/Pierre.Augier/>`_ (CNRS +researcher at `LEGI <http://www.legi.grenoble-inp.fr>`_, Grenoble) at +KTH (Stockholm) as a numerical code to solve fluid equations in a +periodic two-dimensional space with pseudo-spectral methods. + +*Key words and ambitions*: fluid dynamics research with Python (2.7 or +>= 3.3); modular, object-oriented, collaborative, tested and +documented, free and open-source software. + +License +------- + +FluidDyn is distributed under the CeCILL_ License, a GPL compatible +french license. + +.. _CeCILL: http://www.cecill.info/index.en.html + +Installation +------------ + +You can get the source code from `Bitbucket +<https://bitbucket.org/fluiddyn/fluidsim>`__ or from `the Python +Package Index <https://pypi.python.org/pypi/fluidsim/>`__. + +The development mode is often useful. From the root directory:: + + sudo python setup.py develop + +Tests +----- + +From the root directory:: + + make tests + +Or, from the root directory or from any of the "test" directories:: + + python -m unittest discover diff --git a/fluidsim/__init__.py b/fluidsim/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vX19pbml0X18ucHk= --- /dev/null +++ b/fluidsim/__init__.py @@ -0,0 +1,34 @@ +"""Numerical simulations (:mod:`fluidsim`) +================================================ + +.. _simul: +.. currentmodule:: fluidsim + +The package :mod:`fluidsim` **will** provide an object-oriented +toolkit for doing numerical simulations of different equations +(incompressible Navier-Stokes, shallow-water, primitive equations, +with and without the quasi-geostrophic limit, adjoin equations, ...) +with different simple methods (pseudo-spectral, finite differences) +and geometries (1D, 2D and 3D periodic, 1 inhomogeneous direction, +...). + +The package is organised in four sub-packages: + +.. autosummary:: + :toctree: + + util + base + operators + solvers + +""" + +from fluiddyn.io import FLUIDDYN_PATH_SIM as path_dir_results + +from fluidsim.util.util import ( + import_module_solver_from_key, + load_sim_for_plot, load_state_phys_file, + modif_resolution_from_dir, modif_resolution_all_dir) + +from fluidsim.base.params import create_params diff --git a/fluidsim/_version.py b/fluidsim/_version.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vX3ZlcnNpb24ucHk= --- /dev/null +++ b/fluidsim/_version.py @@ -0,0 +1,14 @@ +""" +Module where the version is written. + +It is executed in setup.py and imported in fluiddyn/__init__.py. + +See: + +http://en.wikipedia.org/wiki/Software_versioning +http://legacy.python.org/dev/peps/pep-0386/ + +'a' or 'alpha' means alpha version (internal testing), +'b' or 'beta' means beta version (external testing). +""" +__version__ = '0.0.1a0' diff --git a/fluidsim/base/__init__.py b/fluidsim/base/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9fX2luaXRfXy5weQ== --- /dev/null +++ b/fluidsim/base/__init__.py @@ -0,0 +1,19 @@ +"""Base functionalities for the solvers (:mod:`fluidsim.base`) +==================================================================== + +.. currentmodule:: fluidsim.base + +Provides: + +.. autosummary:: + :toctree: + + solvers + params + state + init_fields + time_stepping + output + forcing + +""" diff --git a/fluidsim/base/forcing/__init__.py b/fluidsim/base/forcing/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9mb3JjaW5nL19faW5pdF9fLnB5 --- /dev/null +++ b/fluidsim/base/forcing/__init__.py @@ -0,0 +1,16 @@ +"""Forcing schemes (:mod:`fluidsim.base.forcing`) +======================================================= + +.. currentmodule:: fluidsim.base.forcing + +Provides: + +.. autosummary:: + :toctree: + + base + specific + +""" + +from .base import ForcingBase, ForcingBasePseudoSpectral diff --git a/fluidsim/base/forcing/base.py b/fluidsim/base/forcing/base.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9mb3JjaW5nL2Jhc2UucHk= --- /dev/null +++ b/fluidsim/base/forcing/base.py @@ -0,0 +1,81 @@ +"""Forcing schemes (:mod:`fluidsim.base.forcing.base`) +============================================================ + +.. currentmodule:: fluidsim.base.forcing.base + +Provides: + +.. autoclass:: ForcingBase + :members: + :private-members: + +.. autoclass:: ForcingBasePseudoSpectral + :members: + :private-members: + +""" + + +class ForcingBase(object): + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + info_solver.classes.Forcing.set_child('classes') + + @staticmethod + def _complete_params_with_default(params, info_solver): + """This static method is used to complete the *params* container. + """ + params.set_child( + 'forcing', + attribs={'type': 'Random', + 'available_types': ['Random', 'Proportional'], + 'forcing_rate': 1, + 'key_forced': 'rot_fft'}) + dict_classes = info_solver.classes.Forcing.import_classes() + for Class in dict_classes.values(): + if hasattr(Class, '_complete_params_with_default'): + try: + Class._complete_params_with_default(params) + except TypeError: + Class._complete_params_with_default(params, info_solver) + + def __init__(self, params, sim): + self.type_forcing = params.forcing.type + + dict_classes = sim.info.solver.classes.Forcing.import_classes() + + if self.type_forcing not in dict_classes: + raise ValueError('Bad value for parameter forcing.type :' + + self.type_forcing) + + ClassForcing = dict_classes[self.type_forcing] + + self._forcing = ClassForcing(params, sim) + + def compute(self): + self._forcing.compute() + + def get_forcing(self): + return self._forcing.forcing_phys + + +class ForcingBasePseudoSpectral(ForcingBase): + + @staticmethod + def _complete_params_with_default(params, info_solver): + """This static method is used to complete the *params* container. + """ + ForcingBase._complete_params_with_default(params, info_solver) + + params.forcing.set_attribs({'nkmax_forcing': 5, 'nkmin_forcing': 4}) + + def compute(self): + self._forcing.compute() + + def get_forcing(self): + return self._forcing.forcing_fft diff --git a/fluidsim/base/forcing/specific.py b/fluidsim/base/forcing/specific.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9mb3JjaW5nL3NwZWNpZmljLnB5 --- /dev/null +++ b/fluidsim/base/forcing/specific.py @@ -0,0 +1,454 @@ +"""Forcing schemes (:mod:`fluidsim.base.forcing.specific`) +================================================================ + +.. currentmodule:: fluidsim.base.forcing.specific + +Provides: + +.. autoclass:: SpecificForcing + :members: + :private-members: + +.. autoclass:: SpecificForcingPseudoSpectral + :members: + :private-members: + +.. autoclass:: NormalizedForcing + :members: + :private-members: + +.. autoclass:: Proportional + :members: + :private-members: + +.. autoclass:: RamdomSimplePseudoSpectral + :members: + :private-members: + +.. autoclass:: TimeCorrelatedRandomPseudoSpectral + :members: + :private-members: + +""" +import numpy as np + +from copy import deepcopy + +from fluiddyn.util import mpi +from fluidsim.operators.operators import OperatorsPseudoSpectral2D +from fluidsim.operators.setofvariables import SetOfVariables + + +class SpecificForcing(object): + + def __init__(self, params, sim): + + self.sim = sim + self.oper = sim.oper + self.params = params + + +class SpecificForcingPseudoSpectral(SpecificForcing): + + def __init__(self, params, sim): + + super(SpecificForcingPseudoSpectral, self).__init__(params, sim) + + self.sum_wavenumbers = sim.oper.sum_wavenumbers + self.fft2 = sim.oper.fft2 + self.ifft2 = sim.oper.ifft2 + + self.forcing_fft = SetOfVariables( + like_this_sov=sim.state.state_fft, + name_type_variables='forcing_fft', value=0.) + self.forcing_fft.initialize(value=0.) + + self.kmax_forcing = self.oper.deltakx*params.forcing.nkmax_forcing + self.kmin_forcing = self.oper.deltakx*params.forcing.nkmin_forcing + self.forcing_rate = params.forcing.forcing_rate + self.key_forced = params.forcing.key_forced + + i = 0 + while 2*params.forcing.nkmax_forcing > 2**i: + i += 1 + n = 2**i + + if mpi.rank == 0: + params_coarse = deepcopy(params) + params_coarse.oper.nx = n + params_coarse.oper.ny = n + params_coarse.oper.type_fft = 'FFTWPY' + params_coarse.oper.coef_dealiasing = 1. + + self.oper_coarse = OperatorsPseudoSpectral2D( + SEQUENCIAL=True, + params=params_coarse, + goal_to_print='coarse resolution for forcing') + self.shapeK_loc_coarse = self.oper_coarse.shapeK_loc + + self.COND_NO_F = np.logical_or( + self.oper_coarse.KK > self.kmax_forcing, + self.oper_coarse.KK < self.kmin_forcing) + + self.nb_forced_modes = (self.COND_NO_F.size - + np.array(self.COND_NO_F, + dtype=np.int32).sum()) + self.ind_forcing = np.logical_not( + self.COND_NO_F).flatten().nonzero()[0] + + else: + self.shapeK_loc_coarse = None + + if mpi.nb_proc > 1: + self.shapeK_loc_coarse = mpi.comm.bcast( + self.shapeK_loc_coarse, root=0) + + # if params.forcing.type_forcing == 'WAVES': + # self.compute = self.compute_forcing_waves + # if mpi.rank == 0: + # eta_rms_max = 0.1 + # self.eta_cond = eta_rms_max / np.sqrt(self.nb_forced_modes) + # print ' eta_cond =', self.eta_cond + # else: + # # self.compute = self.compute_forcing_particular_k + # # self.compute = self.compute_forcing_proportional + # self.compute = self.compute_forcing_2nd_degree_eq + + self.forcingc_fft = SetOfVariables( + keys=self.forcing_fft.keys, + shape1var=self.shapeK_loc_coarse, + dtype=np.complex128, + name_type_variables='forcingc_fft', + value=0.) + + def compute(self): + """compute a forcing normalize with a 2nd degree eq.""" + + a_fft = self.sim.state.state_fft[self.key_forced] + a_fft = self.oper.coarse_seq_from_fft_loc(a_fft, + self.shapeK_loc_coarse) + + if mpi.rank > 0: + Fa_fft = np.empty(self.shapeK_loc_coarse, + dtype=np.complex128) + else: + Fa_fft = self.forcingc_raw_each_time() + self.forcingc_fft[self.key_forced] = Fa_fft + + self.put_forcingc_in_forcing() + + def put_forcingc_in_forcing(self): + """Copy data from forcingc_fft into forcing_fft.""" + nKyc = self.shapeK_loc_coarse[0] + nKxc = self.shapeK_loc_coarse[1] + nb_keys = self.forcing_fft.nb_variables + + ar3Df = self.forcing_fft.data + ar3Dfc = self.forcingc_fft.data + + if mpi.nb_proc > 1: + nKy = self.oper.shapeK_seq[0] + + for ikey in xrange(nb_keys): + fck_fft = ar3Dfc[ikey].transpose() + + for iKxc in xrange(nKxc): + kx = self.oper.deltakx*iKxc + rank_iKx, iKxloc, iKyloc = ( + self.oper.where_is_wavenumber(kx, 0.)) + fc1D = fck_fft[iKxc] + if rank_iKx != 0: + # message fc1D + fc1D = np.ascontiguousarray(fc1D) + if mpi.rank == 0: + mpi.comm.Send([fc1D, mpi.MPI.COMPLEX], + dest=rank_iKx, tag=iKxc) + elif mpi.rank == rank_iKx: + mpi.comm.Recv([fc1D, mpi.MPI.COMPLEX], + source=0, tag=iKxc) + if mpi.rank == rank_iKx: + # copy + for iKyc in xrange(nKyc): + if iKyc <= nKyc/2: + iKy = iKyc + else: + kynodim = iKyc - nKyc + iKy = kynodim + nKy + ar3Df[ikey, iKxloc, iKy] = fc1D[iKyc] + + else: + nKy = self.oper.shapeK_seq[0] + + for ikey in xrange(nb_keys): + for iKyc in xrange(nKyc): + if iKyc <= nKyc/2: + iKy = iKyc + else: + kynodim = iKyc - nKyc + iKy = kynodim + nKy + for iKxc in xrange(nKxc): + ar3Df[ikey, iKy, iKxc] = ar3Dfc[ikey, iKyc, iKxc] + + def verify_injection_rate(self): + """Verify injection rate.""" + Fa_fft = self.forcing_fft[self.key_forced] + a_fft = self.sim.state.state_fft[self.key_forced] + + PZ_forcing1 = abs(Fa_fft)**2/2*self.sim.time_stepping.deltat + PZ_forcing2 = np.real( + Fa_fft.conj()*a_fft + + Fa_fft*a_fft.conj())/2. + PZ_forcing1 = self.oper.sum_wavenumbers(PZ_forcing1) + PZ_forcing2 = self.oper.sum_wavenumbers(PZ_forcing2) + if mpi.rank == 0: + print('PZ_f = {0:9.4e} ; PZ_f2 = {1:9.4e};'.format( + PZ_forcing1 + PZ_forcing2, + PZ_forcing2)) + + +class Proportional(SpecificForcingPseudoSpectral): + + def compute(self): + """Compute a forcing proportional to the flow.""" + a_fft = self.sim.state.state_fft[self.key_forced] + a_fft = self.oper.coarse_seq_from_fft_loc(a_fft, + self.shapeK_loc_coarse) + + if mpi.rank > 0: + Fa_fft = np.empty(self.shapeK_loc_coarse, + dtype=np.complex128) + else: + Fa_fft = self.normalize_forcingc(a_fft) + self.forcingc_fft[self.key_forced] = Fa_fft + + self.put_forcingc_in_forcing() + + # # verification + # self.verify_injection_rate() + + def normalize_forcingc(self, vc_fft): + """Modify the array fvc_fft to fixe the injection rate. + + varc : ndarray + a variable at the coarse resolution. + + To be called only with proc 0. + """ + fvc_fft = vc_fft.copy() + fvc_fft[self.COND_NO_F] = 0. + + Z_fft = abs(fvc_fft)**2/2 + + # # possibly "kill" the largest mode + # nb_kill = 0 + # for ik in xrange(nb_kill): + # imax = Z_fft.argmax() + # Z_fft.flat[imax] = 0. + # fvc_fft.flat[imax] = 0. + + # # possibly add randomness: random kill! + # nb_kill = self.nb_forced_modes-10 + # ind_kill = random.sample(self.ind_forcing,nb_kill) + # for ik in ind_kill: + # Z_fft.flat[ik] = 0. + # fvc_fft.flat[ik] = 0. + + Z = self.oper_coarse.sum_wavenumbers(Z_fft) + deltat = self.sim.time_stepping.deltat + alpha = (np.sqrt(1 + deltat*self.forcing_rate/Z) - 1)/deltat + fvc_fft = alpha*fvc_fft + + return fvc_fft + + +class NormalizedForcing(SpecificForcingPseudoSpectral): + + tag = 'normalized_forcing' + + @classmethod + def _complete_params_with_default(cls, params): + """This static method is used to complete the *params* container. + """ + params.forcing.set_child( + cls.tag, + {'type_normalize': '2nd_degree_eq'}) + + def compute(self): + """compute a forcing normalize with a 2nd degree eq.""" + + a_fft = self.sim.state.state_fft[self.key_forced] + a_fft = self.oper.coarse_seq_from_fft_loc(a_fft, + self.shapeK_loc_coarse) + + if mpi.rank > 0: + Fa_fft = np.empty(self.shapeK_loc_coarse, + dtype=np.complex128) + else: + Fa_fft = self.forcingc_raw_each_time() + Fa_fft = self.normalize_forcingc(Fa_fft, a_fft) + self.forcingc_fft[self.key_forced] = Fa_fft + + self.put_forcingc_in_forcing() + + # # verification + # self.verify_injection_rate() + + def normalize_forcingc(self, fvc_fft, vc_fft): + + type_normalize = self.params.forcing.__dict__[self.tag].type_normalize + + if type_normalize == '2nd_degree_eq': + return self.normalize_forcingc_2nd_degree_eq(fvc_fft, vc_fft) + elif type_normalize == 'particular_k': + return self.normalize_forcingc_part_k(fvc_fft, vc_fft) + else: + ValueError('Bad value for parameter forcing.type_normalize:', + type_normalize) + + def normalize_forcingc_part_k(self, fvc_fft, vc_fft): + """Modify the array fvc_fft to fixe the injection rate. + + varc : ndarray + a variable at the coarse resolution. + + To be called only with proc 0. + """ + oper_c = self.oper_coarse + + oper_c.project_fft_on_realX(fvc_fft) + # fvc_fft[self.COND_NO_F] = 0. + + P_forcing2 = np.real( + + fvc_fft.conj()*vc_fft + + fvc_fft*vc_fft.conj())/2. + P_forcing2 = oper_c.sum_wavenumbers(P_forcing2) + + # we choice randomly a "particular" wavenumber + # in the forced space + KX_f = oper_c.KX[~self.COND_NO_F].flatten() + KY_f = oper_c.KY[~self.COND_NO_F].flatten() + nb_wn_f = len(KX_f) + + ipart = np.random.random_integers(0, nb_wn_f-1) + kx_part = KX_f[ipart] + ky_part = KY_f[ipart] + ikx_part = abs(oper_c.kx_loc-kx_part).argmin() + iky_part = abs(oper_c.ky_loc-ky_part).argmin() + + ik0_part = iky_part + ik1_part = ikx_part + + P_forcing2_part = np.real( + fvc_fft[ik0_part, ik1_part].conj() * + vc_fft[ik0_part, ik1_part] + + fvc_fft[ik0_part, ik1_part] * + vc_fft[ik0_part, ik1_part].conj()) + + if ikx_part == 0: + P_forcing2_part = P_forcing2_part/2 + P_forcing2_other = P_forcing2 - P_forcing2_part + fvc_fft[ik0_part, ik1_part] = \ + -P_forcing2_other/vc_fft[ik0_part, ik1_part].real + + if ikx_part != 0: + fvc_fft[ik0_part, ik1_part] = fvc_fft[ik0_part, ik1_part]/2 + + oper_c.project_fft_on_realX(fvc_fft) + + # normalisation to obtain the wanted total forcing rate + PZ_nonorm = (oper_c.sum_wavenumbers(abs(fvc_fft)**2) * + self.sim.time_stepping.deltat/2 + ) + fvc_fft = fvc_fft*np.sqrt(self.forcing_rate/PZ_nonorm) + + return fvc_fft + + def normalize_forcingc_2nd_degree_eq(self, fvc_fft, vc_fft): + """Modify the array fvc_fft to fixe the injection rate. + + varc : ndarray + a variable at the coarse resolution. + + To be called only with proc 0. + """ + oper_c = self.oper_coarse + + deltat = self.sim.time_stepping.deltat + + a = deltat/2*oper_c.sum_wavenumbers(abs(fvc_fft)**2) + + b = oper_c.sum_wavenumbers( + (vc_fft.conj()*fvc_fft).real) + + c = -self.forcing_rate + + # print 'max abs vc_fft', np.max(abs(vc_fft)) + # print 'max abs fvc_fft', np.max(abs(fvc_fft)) + # print 'in base_forcing:', a, b, c + + Delta = b**2 - 4*a*c + alpha = (np.sqrt(Delta) - b)/(2*a) + + fvc_fft = alpha*fvc_fft + + return fvc_fft + + def coef_normalization_from_abc(self, a, b, c): + """.""" + Delta = b**2 - 4*a*c + alpha = (np.sqrt(Delta) - b)/(2*a) + return alpha + + +class RamdomSimplePseudoSpectral(NormalizedForcing): + + tag = 'random' + + def compute_forcingc_raw(self): + """Random coarse forcing. + + To be called only with proc 0. + """ + F_fft = self.oper_coarse.random_arrayK() + self.oper_coarse.project_fft_on_realX(F_fft) + F_fft[self.COND_NO_F] = 0. + return F_fft + + def forcingc_raw_each_time(self): + return self.compute_forcingc_raw() + + +class TimeCorrelatedRandomPseudoSpectral(RamdomSimplePseudoSpectral): + + def __init__(self, params, sim): + + super(TimeCorrelatedRandomPseudoSpectral, self).__init__(params, sim) + + if mpi.rank == 0: + self.F0 = self.compute_forcingc_raw() + self.F1 = self.compute_forcingc_raw() + self.period_change_F0F1 = self.forcing_rate**(-1./3) + self.t_last_change = self.sim.time_stepping.t + + def forcingc_raw_each_time(self): + tsim = self.sim.time_stepping.t + if tsim-self.t_last_change >= self.period_change_F0F1: + self.t_last_change = tsim + self.F0 = self.F1 + del(self.F1) + self.F1 = self.compute_forcingc_raw() + + F_fft = self.forcingc_from_F0F1() + return F_fft + + def forcingc_from_F0F1(self): + tsim = self.sim.time_stepping.t + deltat = self.period_change_F0F1 + omega = np.pi/deltat + + deltaF = self.F1 - self.F0 + + F_fft = self.F1 - 0.5*( + np.cos((tsim - self.t_last_change)*omega) + 1)*deltaF + + return F_fft diff --git a/fluidsim/base/init_fields.py b/fluidsim/base/init_fields.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9pbml0X2ZpZWxkcy5weQ== --- /dev/null +++ b/fluidsim/base/init_fields.py @@ -0,0 +1,367 @@ +"""Initialisation of the fields (:mod:`fluidsim.base.init_fields`) +======================================================================== + +.. currentmodule:: fluidsim.base.init_fields + +Provides: + +.. autoclass:: InitFieldsBase + :members: + :private-members: + +""" + +import numpy as np +import h5py + +from copy import deepcopy + +from fluiddyn.util import mpi + +from fluidsim.operators.setofvariables import SetOfVariables + + +class InitFieldsBase(object): + """A :class:`InitFieldsBase` object provides functions for + initialisation of 2D fields.""" + + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + attribs = {'type_flow_init': 'NOISE', + 'lambda_noise': 1., + 'max_velo_noise': 1., + # in case type_flow_init == 'LOAD_FILE' + 'path_file': ''} + params.set_child('init_fields', attribs=attribs) + + implemented_flows = ['NOISE', 'CONSTANT', 'LOAD_FILE'] + + def __init__(self, sim=None, oper=None, params=None): + + if sim is not None: + self.sim = sim + params = sim.params + oper = sim.oper + + self.params = params + self.oper = oper + + def get_and_check_type_flow_init(self): + type_flow_init = self.params.init_fields.type_flow_init + if type_flow_init not in self.implemented_flows: + raise ValueError(type_flow_init + ' is not an implemented flows.') + return type_flow_init + + def __call__(self): + sim = self.sim + + type_flow_init = self.get_and_check_type_flow_init() + + if type_flow_init == 'NOISE': + rot_fft, ux_fft, uy_fft = self.init_fields_noise() + sim.state.state_fft['ux_fft'] = ux_fft + sim.state.state_fft['uy_fft'] = uy_fft + sim.state.statephys_from_statefft() + + if type_flow_init == 'LOAD_FILE': + self.get_state_from_file(self.params.init_fields.path_file) + + elif type_flow_init == 'CONSTANT': + sim.state.state_fft.initialize(value=1.) + sim.state.state_phys.initialize(value=1.) + + def init_fields_1dipole(self): + rot = self.vorticity_shape() + rot_fft = self.oper.fft2(rot) + + self.oper.dealiasing(rot_fft) + ux_fft, uy_fft = self.oper.vecfft_from_rotfft(rot_fft) + + return rot_fft, ux_fft, uy_fft + + def vorticity_shape(self): + xs = self.oper.Lx/2 + ys = self.oper.Ly/2 + theta = np.pi/2.3 + b = 2.5 + omega = np.zeros(self.oper.shapeX_loc) + + for ip in range(-1, 2): + for jp in range(-1, 2): + XX_s = (np.cos(theta)*(self.oper.XX-xs-ip*self.oper.Lx) + + np.sin(theta)*(self.oper.YY-ys-jp*self.oper.Ly)) + YY_s = (np.cos(theta)*(self.oper.YY-ys-jp*self.oper.Ly) + - np.sin(theta)*(self.oper.XX-xs-ip*self.oper.Lx)) + omega = omega + self.wz_2LO(XX_s, YY_s, b) + return omega + + def wz_2LO(self, XX, YY, b): + return (- 2*np.exp(-(XX**2 + (YY+b/2)**2)) + + 2*np.exp(-(XX**2 + (YY-b/2)**2))) + + def init_fields_jet(self): + rot = self.vorticity_jet() + rot_fft = self.oper.fft2(rot) + rot_fft[self.oper.KK == 0] = 0. + self.oper.dealiasing(rot_fft) + ux_fft, uy_fft = self.oper.vecfft_from_rotfft(rot_fft) + rot_fft = self.oper.rotfft_from_vecfft(ux_fft, uy_fft) + return rot_fft, ux_fft, uy_fft + + def vorticity_jet(self): + Ly = self.oper.Ly + a = 0.5 + b = Ly/2 + omega0 = 2. + # epsilon = 2. + omega = omega0*( + + np.exp(-((self.oper.YY - Ly/2 + b/2)/a)**2) + - np.exp(-((self.oper.YY - Ly/2 - b/2)/a)**2) + + np.exp(-((self.oper.YY - Ly/2 + b/2 + Ly)/a)**2) + - np.exp(-((self.oper.YY - Ly/2 - b/2 + Ly)/a)**2) + + np.exp(-((self.oper.YY - Ly/2 + b/2 - Ly)/a)**2) + - np.exp(-((self.oper.YY - Ly/2 - b/2 - Ly)/a)**2) + # + epsilon*np.random.random([self.oper.ny_loc, self.oper.nx_loc]) + ) + return omega + + def init_fields_noise(self): + try: + lambda0 = self.params.lambda_noise + except AttributeError: + lambda0 = self.oper.Lx/4 + H_smooth = lambda x, delta: (1. + np.tanh(2*np.pi*x/delta))/2 + + # to compute always the same field... (for 1 resolution...) + np.random.seed(42) # this does not work for MPI... + + ux_fft = (np.random.random(self.oper.shapeK) + + 1j*np.random.random(self.oper.shapeK) - 0.5 - 0.5j) + uy_fft = (np.random.random(self.oper.shapeK) + + 1j*np.random.random(self.oper.shapeK) - 0.5 - 0.5j) + + if mpi.rank == 0: + ux_fft[0, 0] = 0. + uy_fft[0, 0] = 0. + + self.oper.projection_perp(ux_fft, uy_fft) + self.oper.dealiasing(ux_fft, uy_fft) + + k0 = 2*np.pi/lambda0 + delta_k0 = 1.*k0 + ux_fft = ux_fft*H_smooth(k0-self.oper.KK, delta_k0) + uy_fft = uy_fft*H_smooth(k0-self.oper.KK, delta_k0) + + ux = self.oper.ifft2(ux_fft) + uy = self.oper.ifft2(uy_fft) + velo_max = np.sqrt(ux**2+uy**2).max() + if mpi.nb_proc > 1: + velo_max = self.oper.comm.allreduce(velo_max, op=mpi.MPI.MAX) + ux = self.params.init_fields.max_velo_noise*ux/velo_max + uy = self.params.init_fields.max_velo_noise*uy/velo_max + ux_fft = self.oper.fft2(ux) + uy_fft = self.oper.fft2(uy) + + rot_fft = self.oper.rotfft_from_vecfft(ux_fft, uy_fft) + return rot_fft, ux_fft, uy_fft + + def init_fields_noise_rot(self, lambda0): + H_smooth = lambda x, delta: (1. + np.tanh(2*np.pi*x/delta))/2 + rot_fft = (np.random.random([self.nky, self.nkx]) + + 1j*np.random.random([self.nky, self.nkx]) - 0.5 - 0.5j) + k0 = 2*np.pi/lambda0 + delta_k0 = 1*k0 + rot_fft = rot_fft*H_smooth(k0-self.KK, delta_k0) + self.oper.dealiasing(rot_fft) + ux_fft, uy_fft = self.oper.vecfft_from_rotfft(rot_fft) + ux = self.oper.ifft2(ux_fft) + uy = self.oper.ifft2(uy_fft) + velo_max = np.sqrt(ux**2+uy**2).max() + if mpi.nb_proc > 1: + velo_max = self.oper.comm.allreduce(velo_max, op=mpi.MPI.MAX) + ux = ux/velo_max + uy = uy/velo_max + ux_fft = self.oper.fft2(ux) + uy_fft = self.oper.fft2(uy) + + return rot_fft, ux_fft, uy_fft + + def init_fields_wave(self): + ikx = self.sim.params.ikx + eta0 = self.sim.params.eta0 + + # BE CARREFUL, THIS WON'T WORK WITH MPI !!! + if mpi.rank == 0: + print 'init_fields_wave(ikx = {0:4d}, eta0 = {1:7.2e})'.format( + ikx, eta0) + print 'kx[ikx] = {0:8.2f}'.format(self.oper.kxE[ikx]) + + if mpi.nb_proc > 1: + raise ValueError('BE CARREFUL, THIS WILL BE WRONG !' + ' DO NOT USE THIS METHOD WITH MPI ' + '(or rewrite it :-)') + + eta_fft = self.oper.constant_arrayK(value=0.) + ux_fft = self.oper.constant_arrayK(value=0.) + uy_fft = self.oper.constant_arrayK(value=0.) + + eta_fft[0, self.sim.params.ikx] = 0.1*eta0 + # eta_fft[ikx, 0] = 0.1j*eta0 + + self.oper.project_fft_on_realX(eta_fft) + +# ux_fft[0,ikx] = 1.j*eta0 +# uy_fft[0,ikx] = 1.j*eta0 + + div_fft = self.oper.constant_arrayK(value=0.) + div_fft[ikx, 0] = eta0 + div_fft[0, ikx] = eta0 + self.oper.project_fft_on_realX(div_fft) + ux_fft, uy_fft = self.oper.vecfft_from_divfft(div_fft) + + return eta_fft, ux_fft, uy_fft + + def get_state_from_file(self, path_file): + if mpi.rank == 0: + try: + f = h5py.File(path_file, 'r') + except: + raise ValueError('file '+path_file+' is really a hd5 file?') + + print ('Load state from file:\n[...]'+path_file[-75:]) + + try: + group_oper = f['/info_simul/params/oper'] + except: + raise ValueError( + 'file '+path_file+' does not contain a params object') + + try: + group_state_phys = f['/state_phys'] + except: + raise ValueError('file ' + path_file + + ' does not contain a state_phys object') + + nx_file = group_oper.attrs['nx'] + ny_file = group_oper.attrs['ny'] + Lx_file = group_oper.attrs['Lx'] + Ly_file = group_oper.attrs['Ly'] + + if isinstance(nx_file, list): + nx_file = nx_file.item() + ny_file = ny_file.item() + Lx_file = Lx_file.item() + Ly_file = Ly_file.item() + + if self.params.oper.nx != nx_file: + raise ValueError( + 'this is not a correct state for this simulation\n' + 'self.nx != params_file.nx') + + if self.params.oper.ny != ny_file: + raise ValueError( + 'this is not a correct state for this simulation\n' + 'self.ny != params_file.ny') + + if self.params.oper.Lx != Lx_file: + raise ValueError( + 'this is not a correct state for this simulation\n' + 'self.params.oper.Lx != params_file.Lx') + + if self.params.oper.Ly != Ly_file: + raise ValueError( + 'this is not a correct state for this simulation\n' + 'self.params.oper.Ly != params_file.Ly') + + keys_state_phys_file = group_state_phys.keys() + else: + keys_state_phys_file = {} + + if mpi.nb_proc > 1: + keys_state_phys_file = mpi.comm.bcast(keys_state_phys_file) + + state_phys = self.sim.state.state_phys + keys_phys_needed = self.sim.info.solver.classes.State.keys_phys_needed + for k in keys_phys_needed: + if k in keys_state_phys_file: + if mpi.rank == 0: + field_seq = group_state_phys[k][...] + else: + field_seq = self.oper.constant_arrayX() + + if mpi.nb_proc > 1: + field_loc = self.oper.scatter_Xspace(field_seq) + else: + field_loc = field_seq + state_phys[k] = field_loc + else: + state_phys[k] = self.oper.constant_arrayX(value=0.) + + if mpi.rank == 0: + t_file = group_state_phys.attrs['time'] + f.close() + else: + t_file = 0. + + if mpi.nb_proc > 1: + t_file = mpi.comm.bcast(t_file) + + self.sim.state.statefft_from_statephys() + self.sim.state.statephys_from_statefft() + self.sim.time_stepping.t = t_file + + def get_state_from_obj_simul(self, sim_in): + + if mpi.nb_proc > 1: + raise ValueError('BE CARREFUL, THIS WILL BE WRONG !' + ' DO NOT USE THIS METHOD WITH MPI') + + self.sim.time_stepping.t = sim_in.time_stepping.t + + if (self.params.oper.nx == sim_in.params.oper.nx + and self.params.oper.ny == sim_in.params.oper.ny): + state_fft = deepcopy(sim_in.state.state_fft) + else: + # modify resolution + # state_fft = SetOfVariables('state_fft') + state_fft = SetOfVariables(like_this_sov=self.sim.state.state_fft) + keys_state_fft = sim_in.info.solver.classes.State['keys_state_fft'] + for k in keys_state_fft: + field_fft_seq_in = sim_in.state.state_fft[k] + field_fft_seq_new_res = \ + self.sim.oper.constant_arrayK(value=0.) + [nk0_seq, nk1_seq] = field_fft_seq_new_res.shape + [nk0_seq_in, nk1_seq_in] = field_fft_seq_in.shape + + nk0_min = min(nk0_seq, nk0_seq_in) + nk1_min = min(nk1_seq, nk1_seq_in) + + # it is a little bit complicate to take into account ky + for ik1 in xrange(nk1_min): + field_fft_seq_new_res[0, ik1] = field_fft_seq_in[0, ik1] + field_fft_seq_new_res[nk0_min/2, ik1] = \ + field_fft_seq_in[nk0_min/2, ik1] + for ik0 in xrange(1, nk0_min/2): + for ik1 in xrange(nk1_min): + field_fft_seq_new_res[ik0, ik1] = \ + field_fft_seq_in[ik0, ik1] + field_fft_seq_new_res[-ik0, ik1] = \ + field_fft_seq_in[-ik0, ik1] + + state_fft[k] = field_fft_seq_new_res + + if self.sim.output.name_solver == sim_in.output.name_solver: + self.sim.state.state_fft = state_fft + else: # complicated case... untested solution ! + # state_fft = SetOfVariables('state_fft') + raise ValueError('Not yet implemented...') + for k in self.sim.info.solver.classes.State['keys_state_fft']: + if k in sim_in.info.solver.classes.State['keys_state_fft']: + self.sim.state.state_fft[k] = state_fft[k] + else: + self.sim.state.state_fft[k] = \ + self.oper.constant_arrayK(value=0.) + + self.sim.state.statephys_from_statefft() diff --git a/fluidsim/base/output/__init__.py b/fluidsim/base/output/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9vdXRwdXQvX19pbml0X18ucHk= --- /dev/null +++ b/fluidsim/base/output/__init__.py @@ -0,0 +1,31 @@ +"""Output (:mod:`fluidsim.base.output`) +============================================= + +.. currentmodule:: fluidsim.base.output + +Provides: + +.. autosummary:: + :toctree: + + base + prob_dens_func + spectra + phys_fields + spatial_means + time_signalsK + spatial_means + time_signalsK + increments + print_stdout + spect_energy_budget + + +.. autoclass:: OutputBase + :members: + :private-members: + + +""" + +from .base import OutputBase, OutputBasePseudoSpectral diff --git a/fluidsim/base/output/base.py b/fluidsim/base/output/base.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9vdXRwdXQvYmFzZS5weQ== --- /dev/null +++ b/fluidsim/base/output/base.py @@ -0,0 +1,443 @@ +"""Base module for the output (:mod:`fluidsim.base.output.base`) +====================================================================== + +.. currentmodule:: fluidsim.base.output.base + +Provides: + +.. autoclass:: OutputBase + :members: + :private-members: + +.. autoclass:: OutputBasePseudoSpectral + :members: + :private-members: + +.. autoclass:: SpecificOutput + :members: + :private-members: + +""" + +from __future__ import print_function + +import h5py +import matplotlib.pyplot as plt +import datetime +import os +import shutil +import numpy as np + + +import fluiddyn + +from fluiddyn.util import mpi + +from fluiddyn.io import FLUIDDYN_PATH_SIM, FLUIDDYN_PATH_SCRATCH + +from fluiddyn.util.util import time_as_str, print_memory_usage + +from fluidsim.util.util import load_params_simul + + +class OutputBase(object): + """Handle the output.""" + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + info_solver.classes.Output.set_child('classes') + classes = info_solver.classes.Output.classes + + classes.set_child( + 'PrintStdOut', + attribs={'module_name': 'fluidsim.base.output.print_stdout', + 'class_name': 'PrintStdOutBase'}) + + classes.set_child( + 'PhysFields', + attribs={'module_name': 'fluidsim.base.output.phys_fields', + 'class_name': 'PhysFieldsBase'}) + + @staticmethod + def _complete_params_with_default(params, info_solver): + """This static method is used to complete the *params* container. + """ + attribs = {'period_show_plot': 1, + 'ONLINE_PLOT_OK': True, + 'HAS_TO_SAVE': True} + params.set_child('output', attribs=attribs) + + params.output.set_child('periods_save') + params.output.set_child('periods_print') + params.output.set_child('periods_plot') + + dict_classes = info_solver.classes.Output.import_classes() + for Class in dict_classes.values(): + if hasattr(Class, '_complete_params_with_default'): + try: + Class._complete_params_with_default(params) + except TypeError: + Class._complete_params_with_default(params, info_solver) + + def __init__(self, sim): + params = sim.params + self.sim = sim + self.params = params.output + + self.has_to_save = self.params.HAS_TO_SAVE + self.name_solver = sim.info.solver['short_name'] + + # initialisation name_run and path_run + list_for_name_run = self.create_list_for_name_run() + list_for_name_run.append(time_as_str()) + self.name_run = '_'.join(list_for_name_run) + + self.sim.name_run = self.name_run + + if not params.NEW_DIR_RESULTS: + try: + self.path_run = params.path_run + except AttributeError: + params.NEW_DIR_RESULTS = True + print('Strange: params.NEW_DIR_RESULTS == False ' + 'but no params.path_run') + + # if has_to_save, we verify the correspondence between the + # resolution of the simulation and the resolution of the + # previous simulation saved in this directory + if self.has_to_save: + if mpi.rank == 0: + try: + params_dir = load_params_simul(path_dir=self.path_run) + except: + raise ValueError( + 'Strange, no info_simul.h5 in self.path_run') + + if (params.oper.nx != params_dir.oper.nx + or params.oper.ny != params_dir.oper.ny): + params.NEW_DIR_RESULTS = True + print(""" +Warning: params.NEW_DIR_RESULTS is False but the resolutions of the simulation + and of the simulation in the directory self.path_run are different + we put params.NEW_DIR_RESULTS = True""") + if mpi.nb_proc > 1: + params.NEW_DIR_RESULTS = \ + mpi.comm.bcast(params.NEW_DIR_RESULTS) + + if params.NEW_DIR_RESULTS: + + if FLUIDDYN_PATH_SCRATCH is not None: + self.path_run = os.path.join( + FLUIDDYN_PATH_SCRATCH, self.sim.name_run) + else: + self.path_run = os.path.join( + FLUIDDYN_PATH_SIM, self.sim.name_run) + + if mpi.rank == 0: + params._set_attr_xml('path_run', self.path_run) + if not os.path.exists(self.path_run): + os.makedirs(self.path_run) + + dico_classes = sim.info.solver.classes.Output.import_classes() + + PrintStdOut = dico_classes['PrintStdOut'] + self.print_stdout = PrintStdOut(self) + + if not self.params.ONLINE_PLOT_OK: + for k in self.params.periods_plot.xml_attrib.keys(): + self.params.periods_plot[k] = 0. + + if not self.has_to_save: + for k in self.params.periods_save.xml_attrib.keys(): + self.params.periods_save[k] = 0. + + def create_list_for_name_run(self): + list_for_name_run = [self.name_solver] + if len(self.sim.params.short_name_type_run) > 0: + list_for_name_run.append(self.sim.params.short_name_type_run) + list_for_name_run.append(self.sim.oper.produce_str_describing_oper()) + + return list_for_name_run + + def init_with_oper_and_state(self): + sim = self.sim + + self.oper = sim.oper + + if mpi.rank == 0: + # print info on the run + specifications = (', '+sim.params.time_stepping.type_time_scheme + + # ', '+self.oper.type_fft + + ' and ') + if mpi.nb_proc == 1: + specifications = specifications+'sequenciel,\n' + else: + specifications += 'parallel ({0} proc.)\n'.format(mpi.nb_proc) + self.print_stdout( + '\nsolver ' + self.name_solver + specifications + + self.sim.oper.produce_long_str_describing_oper() + + 'path_run =\n' + self.path_run + '\n' + + 'type_flow_init = ' + sim.params.init_fields.type_flow_init) + + if mpi.rank == 0 and self.has_to_save and sim.params.NEW_DIR_RESULTS: + # save info on the run + self.sim.info.solver.xml_save( + path_file=self.path_run+'/info_solver.xml', + comment=( + 'This file has been created by' + ' the Python program FluidDyn ' + fluiddyn.__version__ + + '.\n\nIt should not be modified ' + '(except for adding xml comments).')) + + self.sim.params.xml_save( + path_file=self.path_run+'/params_simul.xml', + comment=( + 'This file has been created by' + ' the Python program FluidDyn ' + fluiddyn.__version__ + + '.\n\nIt should not be modified ' + '(except for adding xml comments).')) + + if mpi.rank == 0: + plt.ion() + self.print_stdout('Initialization outputs:') + + self.print_stdout.complete_init_with_state() + + dico_classes = sim.info.solver.classes.Output.import_classes() + + # This class has already been instantiated. + dico_classes.pop('PrintStdOut') + + for Class in dico_classes.values(): + if mpi.rank == 0: + print(Class, Class._tag) + self.__dict__[Class._tag] = Class(self) + + print_memory_usage( + '\nMemory usage at the end of init. (equiv. seq.)') + + try: + self.print_size_in_Mo(self.sim.state.state_fft, 'state_fft') + except AttributeError: + self.print_size_in_Mo(self.sim.state.state_phys, 'state_phys') + + def one_time_step(self): + + for k in self.params.periods_print.xml_attrib.keys(): + period = self.params.periods_print.__dict__[k] + if period != 0: + self.__dict__[k].online_print() + + if self.params.ONLINE_PLOT_OK: + for k in self.params.periods_plot.xml_attrib.keys(): + period = self.params.periods_plot.__dict__[k] + if period != 0: + self.__dict__[k].online_plot() + + if self.has_to_save: + for k in self.params.periods_save.xml_attrib.keys(): + period = self.params.periods_save.__dict__[k] + if period != 0: + self.__dict__[k].online_save() + + def figure_axe(self, numfig=None, size_axe=None): + if mpi.rank == 0: + if size_axe is None: + x_left_axe = 0.12 + z_bottom_axe = 0.1 + width_axe = 0.85 + height_axe = 0.84 + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + if numfig is None: + fig = plt.figure() + else: + fig = plt.figure(numfig) + fig.clf() + axe = fig.add_axes(size_axe) + return fig, axe + + def end_of_simul(self, total_time): + self.print_stdout( + 'Computation completed in {0:8.6g} s\n'.format(total_time) + + 'path_run =\n'+self.path_run) + if self.has_to_save: + self.phys_fields.save() + if mpi.rank == 0 and self.has_to_save: + self.print_stdout.close() + + for k in self.params.periods_save.xml_attrib.keys(): + period = self.params.periods_save.__dict__[k] + if period != 0: + if hasattr(self.__dict__[k], 'close_file'): + self.__dict__[k].close_file() + + if (not self.path_run.startswith(FLUIDDYN_PATH_SIM) and mpi.rank == 0): + new_path_run = os.path.join(FLUIDDYN_PATH_SIM, self.sim.name_run) + print('move result directory in directory:\n'+new_path_run) + shutil.move(self.path_run, FLUIDDYN_PATH_SIM) + self.path_run = new_path_run + + def compute_energy(self): + return 0. + + def print_size_in_Mo(self, arr, string=None): + if string is None: + string = 'Size of ndarray (equiv. seq.)' + else: + string = 'Size of '+string+' (equiv. seq.)' + mem = arr.nbytes*1.e-6 + if mpi.nb_proc > 1: + mem = mpi.comm.allreduce(mem, op=mpi.MPI.SUM) + self.print_stdout(string.ljust(30)+': {0} Mo'.format(mem)) + + +class OutputBasePseudoSpectral(OutputBase): + + def init_with_oper_and_state(self): + + oper = self.sim.oper + self.sum_wavenumbers = oper.sum_wavenumbers + self.fft2 = oper.fft2 + self.ifft2 = oper.ifft2 + # really necessary here? + self.vecfft_from_rotfft = oper.vecfft_from_rotfft + self.rotfft_from_vecfft = oper.rotfft_from_vecfft + + super(OutputBasePseudoSpectral, self).init_with_oper_and_state() + + +class SpecificOutput(object): + """Small class for features useful for specific outputs""" + + def __init__(self, output, name_file=None, + period_save=0, period_plot=0, + has_to_plot_saved=False, + dico_arrays_1time=None): + + sim = output.sim + params = sim.params + + self.output = output + self.sim = sim + self.oper = sim.oper + self.params = params + + self.period_save = period_save + self.period_plot = period_plot + self.has_to_plot = has_to_plot_saved + + if not params.output.ONLINE_PLOT_OK: + self.period_plot = 0 + self.has_to_plot = False + + if not has_to_plot_saved: + if self.period_plot > 0: + self.has_to_plot = True + else: + self.has_to_plot = False + + self.period_show = params.output.period_show_plot + self.t_last_show = 0. + + if name_file is not None: + self.path_file = self.output.path_run + '/' + name_file + else: + self.init_path_files() + + if self.has_to_plot and mpi.rank == 0: + self.init_online_plot() + + if not output.has_to_save: + self.period_save = 0. + + if self.period_save != 0.: + self.init_files(dico_arrays_1time) + + def init_path_files(self): + pass + + def init_files(self, dico_arrays_1time=None): + if dico_arrays_1time is None: + dico_arrays_1time = {} + dico_results = self.compute() + if mpi.rank == 0: + if not os.path.exists(self.path_file): + self.create_file_from_dico_arrays( + self.path_file, dico_results, dico_arrays_1time) + self.nb_saved_times = 1 + else: + with h5py.File(self.path_file, 'r') as f: + dset_times = f['times'] + self.nb_saved_times = dset_times.shape[0]+1 + self.add_dico_arrays_to_file(self.path_file, + dico_results) + self.t_last_save = self.sim.time_stepping.t + + def online_save(self): + """Save the values at one time. """ + tsim = self.sim.time_stepping.t + if (tsim - self.t_last_save >= self.period_save): + self.t_last_save = tsim + dico_results = self.compute() + if mpi.rank == 0: + self.add_dico_arrays_to_file(self.path_file, + dico_results) + self.nb_saved_times += 1 + if self.has_to_plot: + self._online_plot(dico_results) + if (tsim - self.t_last_show >= self.period_show): + self.t_last_show = tsim + self.fig.canvas.draw() + + def create_file_from_dico_arrays(self, path_file, + dico_arrays, dico_arrays_1time): + if os.path.exists(path_file): + print('file NOT created since it already exists!') + elif mpi.rank == 0: + with h5py.File(path_file, 'w') as f: + f.attrs['date saving'] = str(datetime.datetime.now()) + f.attrs['name_solver'] = self.output.name_solver + f.attrs['name_run'] = self.output.name_run + + self.sim.info.xml_to_hdf5(hdf5_parent=f) + + times = np.array([self.sim.time_stepping.t]) + f.create_dataset( + 'times', data=times, maxshape=(None,)) + + for k, v in dico_arrays_1time.iteritems(): + f.create_dataset(k, data=v) + + for k, v in dico_arrays.iteritems(): + v.resize([1, v.size]) + f.create_dataset( + k, data=v, maxshape=(None, v.size)) + + def add_dico_arrays_to_file(self, path_file, dico_arrays): + if not os.path.exists(path_file): + raise ValueError('can not add dico arrays in nonexisting file!') + elif mpi.rank == 0: + with h5py.File(path_file, 'r+') as f: + dset_times = f['times'] + nb_saved_times = dset_times.shape[0] + dset_times.resize((nb_saved_times+1,)) + dset_times[nb_saved_times] = self.sim.time_stepping.t + for k, v in dico_arrays.iteritems(): + dset_k = f[k] + dset_k.resize((nb_saved_times+1, v.size)) + dset_k[nb_saved_times] = v + + def add_dico_arrays_to_open_file(self, f, dico_arrays, nb_saved_times): + if mpi.rank == 0: + dset_times = f['times'] + dset_times.resize((nb_saved_times+1,)) + dset_times[nb_saved_times] = self.sim.time_stepping.t + for k, v in dico_arrays.iteritems(): + dset_k = f[k] + dset_k.resize((nb_saved_times+1, v.size)) + dset_k[nb_saved_times] = v diff --git a/fluidsim/base/output/increments.py b/fluidsim/base/output/increments.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9vdXRwdXQvaW5jcmVtZW50cy5weQ== --- /dev/null +++ b/fluidsim/base/output/increments.py @@ -0,0 +1,909 @@ + + +from __future__ import division, print_function + +import h5py +import os +import numpy as np + +from fluiddyn.util import mpi + +from .base import SpecificOutput + + +class Increments(SpecificOutput): + """A :class:`Increments` object handles the saving of pdf of + increments. + """ + + _tag = 'increments' + + @staticmethod + def _complete_params_with_default(params): + tag = 'increments' + + params.output.periods_save.set_attrib(tag, 0) + params.output.set_child(tag, + attribs={'HAS_TO_PLOT_SAVED': False}) + + def __init__(self, output): + params = output.sim.params + self.nx = params.oper.nx + + self.nrx = min(self.nx//16, 128) + self.nrx = max(self.nrx, self.nx//2) + rmin = 1 + rmax = int(0.8*self.nx) + delta_logr = np.log(rmax/rmin)/(self.nrx-1) + logr = np.log(rmin) + delta_logr*np.arange(self.nrx) + self.rxs = np.array(np.round(np.exp(logr)), dtype=np.int32) + + for ir in xrange(1, self.nrx): + if self.rxs[ir-1] >= self.rxs[ir]: + self.rxs[ir] = self.rxs[ir-1] + 1 + + self.nbins = 400 + + name_file = 'increments.h5' + self.path_file = output.path_run+'/'+name_file + + if os.path.exists(self.path_file): + if mpi.rank == 0: + with h5py.File(self.path_file, 'r') as f: + self.rxs = f['rxs'][...] + self.nbins = f['nbins'][...] + if mpi.nb_proc > 1: + self.rxs = mpi.comm.bcast(self.rxs) + self.nbins = mpi.comm.bcast(self.nbins) + + self.nrx = self.rxs.size + dico_arrays_1time = { + 'rxs': self.rxs, + 'nbins': self.nbins} + + self.keys_vars_to_compute = list(output.sim.state.state_phys.keys) + + super(Increments, self).__init__( + output, + name_file=name_file, + period_save=params.output.periods_save.increments, + has_to_plot_saved=params.output.increments.HAS_TO_PLOT_SAVED, + dico_arrays_1time=dico_arrays_1time) + + def init_online_plot(self): + self.fig, axe = self.output.figure_axe(numfig=5000000) + self.axe = axe + axe.set_xlabel('$\delta u_x (x)$') + axe.set_ylabel('pdf') + axe.set_title( + 'pdf $\delta u_x (x)$, solver ' + self.output.name_solver + + ', nh = {0:5d}'.format(self.nx)) + axe.hold(True) + + def _online_plot(self, dico_results, key='rot'): + """online plot on pdf""" + pdf = dico_results['pdf_delta_'+key] + pdf = pdf.reshape([self.nrx, self.nbins]) + valmin = dico_results['valmin_'+key] + valmax = dico_results['valmax_'+key] + + for irx, rx in enumerate(self.rxs): + values_inc = self.compute_values_inc( + valmin[irx], valmax[irx]) + self.axe.plot(values_inc+irx, pdf[irx]) + + def compute(self): + """compute the values at one time.""" + dico_results = {} + for key in self.keys_vars_to_compute: + var = self.sim.state(key) + + pdf_var = np.empty([self.nrx, self.nbins]) + valmin = np.empty([self.nrx]) + valmax = np.empty([self.nrx]) + + for irx, rx in enumerate(self.rxs): + inc_var = self.oper.compute_increments_dim1(var, rx) + (pdf_var[irx], bin_edges_var + ) = self.oper.pdf_normalized(inc_var, self.nbins) + valmin[irx] = bin_edges_var[0] + valmax[irx] = bin_edges_var[self.nbins] + + dico_results['pdf_delta_'+key] = pdf_var.flatten() + dico_results['valmin_'+key] = valmin + dico_results['valmax_'+key] = valmax + + return dico_results + + def compute_values_inc(self, valmin, valmax): + return (valmin + + (valmax-valmin)/self.nbins*np.arange(0.5, self.nbins)) + + def load(self): + """load the saved pdf and return a dictionary.""" + f = h5py.File(self.path_file, 'r') + dset_times = f['times'] + times = dset_times[...] + + list_base_keys = ['pdf_delta_', 'valmin_', 'valmax_', 'struc_func_'] + + dico_results = {'times': times} + for key in self.keys_vars_to_compute: + for base_key in list_base_keys: + dset_pdf = f[base_key+key] + result = dset_pdf[...] + dico_results[base_key+key] = result + + return dico_results + + def plot(self, tmin=0, tmax=None, delta_t=2, order=2, yscale='log'): + """Plot some structure functions.""" + f = h5py.File(self.path_file, 'r') + dset_times = f['times'] + times = dset_times[...] + # nt = len(times) + + if tmax is None: + tmax = times.max() + + rxs = f['rxs'][...] + + oper = f['/info_simul/params/oper'] + nx = oper.attrs['nx'] + Lx = oper.attrs['Lx'] + deltax = Lx/nx + + rxs = np.array(rxs, dtype=np.float64)*deltax + + # orders = f['orders'][...] + # dset_struc_func_ux = f['struc_func_ux'] + # dset_struc_func_uy = f['struc_func_uy'] + + delta_t_save = np.mean(times[1:]-times[0:-1]) + delta_i_plot = int(np.round(delta_t/delta_t_save)) + if delta_i_plot == 0 and delta_t != 0.: + delta_i_plot = 1 + delta_t = delta_i_plot*delta_t_save + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + + to_print = 'plot(tmin={0}, tmax={1}, delta_t={2:.2f})'.format( + tmin, tmax, delta_t) + print(to_print) + + to_print = ('plot structure functions\n' + 'tmin = {0:8.6g} ; tmax = {1:8.6g} ; delta_t = {2:8.6g}\n' + 'imin = {3:8d} ; imax = {4:8d} ; delta_i = {5:8d}').format( + tmin_plot, tmax_plot, delta_t, + imin_plot, imax_plot, delta_i_plot) + print(to_print) + + + pdf_ux, values_inc_ux, nb_rx_to_plot = self.load_pdf_from_file( + tmin=tmin, tmax=tmax, key_var='ux') + + pdf_uy, values_inc_uy, nb_rx_to_plot = self.load_pdf_from_file( + tmin=tmin, tmax=tmax, key_var='uy') + + # iorder = self.iorder_from_order(order) + order = float(order) + + x_left_axe = 0.12 + z_bottom_axe = 0.56 + width_axe = 0.85 + height_axe = 0.37 + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('$r_x$') + ax1.set_ylabel(r'$\langle \delta u^{' + + '{0}'.format(order) + '} \\rangle$') + + ax1.set_title('struct. functions, solver '+self.output.name_solver+ + ', nh = {0:5d}'.format(self.nx)) + ax1.hold(True) + ax1.set_xscale('log') + ax1.set_yscale(yscale) + + So_ux = self.strfunc_from_pdf(pdf_ux, values_inc_ux, order) + So_uy = self.strfunc_from_pdf(pdf_uy, values_inc_uy, order) + + norm = rxs + + # ax1.set_ylabel('struct. functions, order = {0}'.format(order)) + # if delta_t != 0.: + # for it in xrange(imin_plot,imax_plot+1,delta_i_plot): + # struc_func_ux = dset_struc_func_ux[it] + # struc_func_ux = struc_func_ux.reshape( + # [self.norders, self.nrx]) + # struc_func_uy = dset_struc_func_uy[it] + # struc_func_uy = struc_func_uy.reshape( + # [self.norders, self.nrx]) + + # ax1.plot(rxs, struc_func_ux[iorder], 'c', linewidth=1) + # ax1.plot(rxs, struc_func_uy[iorder], 'm', linewidth=1) + + # struc_func_ux = dset_struc_func_ux[imin_plot:imax_plot+1].mean(0) + # struc_func_ux = struc_func_ux.reshape([self.norders, self.nrx]) + # struc_func_uy = dset_struc_func_uy[imin_plot:imax_plot+1].mean(0) + # struc_func_uy = struc_func_uy.reshape([self.norders, self.nrx]) + + # ax1.plot(rxs, struc_func_ux[iorder]/norm, 'c', linewidth=2) + # ax1.plot(rxs, struc_func_uy[iorder]/norm, 'm', linewidth=2) + + ax1.plot(rxs, So_ux/norm, 'c-.', linewidth=2) + ax1.plot(rxs, So_uy/norm, 'm-.', linewidth=2) + if order % 2 == 1: + ax1.plot(rxs, -So_ux/norm, 'c:', linewidth=2) + ax1.plot(rxs, -So_uy/norm, 'm:', linewidth=2) + + # ax1.plot(rxs, abs(struc_func_ux[iorder])/abs(struc_func_uy[iorder]), + # 'k', linewidth=1) + + ax1.plot(rxs, abs(So_ux)/abs(So_uy), + 'k', linewidth=1) + + # if self.orders[iorder]%2 == 1: + # ax1.plot(rxs, -struc_func_ux[iorder]/norm, '--b', linewidth=2) + # ax1.plot(rxs, -struc_func_uy[iorder]/norm, '--m', linewidth=2) + + cond = rxs < 6*deltax + ax1.plot(rxs[cond], 1.e4*rxs[cond]**(order)/norm[cond], + 'k', linewidth=2) + ax1.plot(rxs, rxs**(order/3)/norm, '--k', linewidth=2) + + ax1.plot(rxs, 1.e0*rxs**(1)/norm, ':k', linewidth=2) + + + z_bottom_axe = 0.09 + size_axe[1] = z_bottom_axe + ax2 = fig.add_axes(size_axe) + + + ax2.set_xlabel('$r_x$') + ax2.set_ylabel('flatness') + ax2.set_xscale('log') + ax2.set_yscale('log') + + # iorder4 = self.iorder_from_order(4) + # iorder2 = self.iorder_from_order(2) + + # if delta_t != 0.: + # for it in xrange(imin_plot,imax_plot+1,delta_i_plot): + # struc_func_ux = dset_struc_func_ux[it] + # struc_func_ux = struc_func_ux.reshape( + # [self.norders, self.nrx]) + # struc_func_uy = dset_struc_func_uy[it] + # struc_func_uy = struc_func_uy.reshape( + # [self.norders, self.nrx]) + + # flatnessL = struc_func_ux[iorder4]/struc_func_ux[iorder2]**2 + # flatnessT = struc_func_uy[iorder4]/struc_func_uy[iorder2]**2 + + # ax2.plot(rxs, flatnessL, 'c', linewidth=1) + # ax2.plot(rxs, flatnessT, 'm', linewidth=1) + + # struc_func_ux = dset_struc_func_ux[imin_plot:imax_plot+1].mean(0) + # struc_func_ux = struc_func_ux.reshape([self.norders, self.nrx]) + # struc_func_uy = dset_struc_func_uy[imin_plot:imax_plot+1].mean(0) + # struc_func_uy = struc_func_uy.reshape([self.norders, self.nrx]) + + # flatnessL = struc_func_ux[iorder4]/struc_func_ux[iorder2]**2 + # flatnessT = struc_func_uy[iorder4]/struc_func_uy[iorder2]**2 + # ax2.plot(rxs, flatnessL, 'c', linewidth=2) + # ax2.plot(rxs, flatnessT, 'm', linewidth=2) + + S2_ux = self.strfunc_from_pdf(pdf_ux, values_inc_ux, 2) + S2_uy = self.strfunc_from_pdf(pdf_uy, values_inc_uy, 2) + + S4_ux = self.strfunc_from_pdf(pdf_ux, values_inc_ux, 4) + S4_uy = self.strfunc_from_pdf(pdf_uy, values_inc_uy, 4) + + flatnessL_bis = S4_ux/S2_ux**2 + flatnessT_bis = S4_uy/S2_uy**2 + + ax2.plot(rxs, flatnessL_bis, 'c--', linewidth=2) + ax2.plot(rxs, flatnessT_bis, 'm--', linewidth=2) + + + cond = np.logical_and(rxs < 70*deltax, + rxs > 5*deltax) + ax2.plot(rxs[cond], 1e1*rxs[cond]**(-1), ':k', linewidth=2) + + ax2.plot(rxs, 3*np.ones(rxs.shape), 'k--', linewidth=0.5) + + + def strfunc_from_pdf(self, pdf, values, order, absolute=False): + order = float(order) + S_order = np.empty(self.rxs.shape) + if absolute: + values = abs(values) + for irx in xrange(self.rxs.size): + deltainc = abs(values[irx, 1] - values[irx, 0]) + S_order[irx] = deltainc*np.sum( + pdf[irx]*values[irx]**order) + return S_order + + + def load_pdf_from_file(self, tmin=0, tmax=None, key_var='ux', + irx_to_plot=None): + """Plot some pdf.""" + f = h5py.File(self.path_file, 'r') + dset_times = f['times'] + times = dset_times[...] + nt = len(times) + + if tmax is None: + tmax = times.max() + + rxs = f['rxs'][...] + + oper = f['/info_simul/params/oper'] + nx = oper.attrs['nx'] + Lx = oper.attrs['Lx'] + + deltax = Lx/nx + + rxs = np.array(rxs, dtype=np.float64)*deltax + + # orders = f['orders'][...] + + delta_t_save = np.mean(times[1:]-times[0:-1]) + delta_t = delta_t_save + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + + +# to_print = '''load pdf of the increments +# tmin = {0:8.6g} ; tmax = {1:8.6g} +# imin = {2:8d} ; imax = {3:8d}'''.format( +# tmin_plot, tmax_plot, +# imin_plot, imax_plot) +# print(to_print) + + + if irx_to_plot is None: + irx_to_plot = np.arange(rxs.size) + + nb_rx_to_plot = irx_to_plot.size + + # print 'irx_to_plot', irx_to_plot + # print 'self.rxs[irx_to_plot]', self.rxs[irx_to_plot] + + pdf_timemean = np.zeros([nb_rx_to_plot, self.nbins]) + values_inc_timemean = np.zeros([nb_rx_to_plot, self.nbins]) + + valmin_timemean = np.zeros([nb_rx_to_plot]) + valmax_timemean = np.zeros([nb_rx_to_plot]) + nb_timemean = 0 + + for it in xrange(imin_plot, imax_plot+1): + nb_timemean += 1 + valmin = f['valmin_'+key_var][it] + valmax = f['valmax_'+key_var][it] + + for irxp, irx in enumerate(irx_to_plot): + valmin_timemean[irxp] += valmin[irx] + valmax_timemean[irxp] += valmax[irx] + + valmin_timemean /= nb_timemean + valmax_timemean /= nb_timemean + + for irxp, irx in enumerate(irx_to_plot): + values_inc_timemean[irxp] = self.compute_values_inc( + valmin_timemean[irxp], valmax_timemean[irxp]) + + nt = 0 + for it in xrange(imin_plot, imax_plot+1): + nt += 1 + pdf_dvar2D = f['pdf_delta_'+key_var][it] + pdf_dvar2D = pdf_dvar2D.reshape([self.nrx, self.nbins]) + valmin = f['valmin_'+key_var][it] + valmax = f['valmax_'+key_var][it] + + for irxp, irx in enumerate(irx_to_plot): + pdf_dvar = pdf_dvar2D[irx] + values_inc = self.compute_values_inc( + valmin[irx], valmax[irx]) + + pdf_timemean[irxp] += np.interp( + values_inc_timemean[irxp], values_inc, pdf_dvar) + + pdf_timemean /= nt + + + return pdf_timemean, values_inc_timemean, nb_rx_to_plot + + + + + + def plot_pdf(self, tmin=0, tmax=None, key_var='ux', + order=0, nb_rx_to_plot=5 + ): + + irx_to_plot = np.arange(0, self.rxs.size, self.rxs.size/nb_rx_to_plot) + nb_rx_to_plot = irx_to_plot.size + + (pdf_timemean, values_inc_timemean, nb_rx_to_plot + ) = self.load_pdf_from_file(tmin=tmin, tmax=tmax, key_var=key_var, + irx_to_plot=irx_to_plot) + + + to_print = 'plot_pdf(tmin={0}, tmax={1})'.format( + tmin, tmax) + print(to_print) + + + + fig, ax1 = self.output.figure_axe() + ax1.set_title('pdf increments, solver '+self.output.name_solver+ +', nh = {0:5d}'.format(self.nx)) +# +', c2 = {0:.4g}, f = {1:.4g}'.format(self.c2, self.f)) + ax1.hold(True) + ax1.set_xscale('linear') + ax1.set_yscale('linear') + + + ax1.set_xlabel(key_var) + ax1.set_ylabel('PDF x $\delta v^'+repr(order)+'$') + + colors = ['k', 'y', 'r', 'b', 'g', 'm', 'c'] + + for irxp, irx in enumerate(irx_to_plot): + + print('color = {0}, rx = {1}'.format(colors[irxp], self.rxs[irx])) + + val_inc = values_inc_timemean[irxp] + + ax1.plot(val_inc, pdf_timemean[irxp]*val_inc**order, + colors[irxp]+'x-', linewidth=1) + + + + + + + + # def iorder_from_order(self, order): + # """Return the indice corresponding to one value of order.""" + # iorder = abs(self.orders-order).argmin() + # if self.orders[iorder] != order: + # raise ValueError( + # 'Order {0} has not been computed ?'.format(order) + # ) + # return iorder + + + + + + + + + + + + + +class IncrementsSW1l(Increments): + """A :class:`Increments` object handles the saving of pdf of + increments. + """ + + def __init__(self, output): + super(IncrementsSW1l, self).__init__(output) + params = output.sim.params + self.c2 = params.c2 + self.f = params.f + + + + def _online_plot(self, dico_results, key='eta'): + """online plot on pdf""" + super(IncrementsSW1l, self)._online_plot(dico_results, key=key) + + + + def compute(self): + dico_results = super(IncrementsSW1l, self).compute() + + ux = self.sim.state('ux') + uy = self.sim.state('uy') + eta = self.sim.state('eta') + Jx = (1+eta)*ux + + S_uL2JL = np.empty([self.nrx]) + S_uT2JL = np.empty([self.nrx]) + S_c2h2uL = np.empty([self.nrx]) + S_uT2uL = np.empty([self.nrx]) + + for irx, rx in enumerate(self.rxs): + inc_ux = self.oper.compute_increments_dim1(ux, rx) + inc_uy = self.oper.compute_increments_dim1(uy, rx) + inc_eta = self.oper.compute_increments_dim1(eta, rx) + inc_Jx = self.oper.compute_increments_dim1(Jx, rx) + inc_uy2 = inc_uy**2 + S_uL2JL[irx] = np.mean(inc_ux**2*inc_Jx) + S_uT2JL[irx] = np.mean(inc_uy2*inc_Jx) + S_c2h2uL[irx] = self.params.c2*np.mean(inc_eta**2*inc_ux) + S_uT2uL[irx] = np.mean(inc_uy2*inc_ux) + + dico_results['struc_func_uL2JL'] = S_uL2JL + dico_results['struc_func_uT2JL'] = S_uT2JL + dico_results['struc_func_c2h2uL'] = S_c2h2uL + dico_results['struc_func_Kolmo'] = S_uL2JL + S_uT2JL + S_c2h2uL + + dico_results['struc_func_uT2uL'] = S_uT2uL + + return dico_results + + + + + def plot(self, tmin=0, tmax=None, delta_t=2, order=2, yscale='log'): + """Plot some structure functions.""" + f = h5py.File(self.path_file, 'r') + dset_times = f['times'] + times = dset_times[...] + # nt = len(times) + + if tmax is None: + tmax = times.max() + + rxs = f['rxs'][...] + + oper = f['/info_simul/params/oper'] + nx = oper.attrs['nx'] + Lx = oper.attrs['Lx'] + deltax = Lx/nx + + rxs = np.array(rxs, dtype=np.float64)*deltax + + # orders = f['orders'][...] + # dset_struc_func_ux = f['struc_func_ux'] + # dset_struc_func_uy = f['struc_func_uy'] + + delta_t_save = np.mean(times[1:]-times[0:-1]) + delta_i_plot = int(np.round(delta_t/delta_t_save)) + if delta_i_plot == 0 and delta_t != 0.: + delta_i_plot=1 + delta_t = delta_i_plot*delta_t_save + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + + to_print = 'plot(tmin={0}, tmax={1}, delta_t={2:.2f})'.format( + tmin, tmax, delta_t) + print(to_print) + + to_print = '''plot structure functions +tmin = {0:8.6g} ; tmax = {1:8.6g} ; delta_t = {2:8.6g} +imin = {3:8d} ; imax = {4:8d} ; delta_i = {5:8d}'''.format( +tmin_plot, tmax_plot, delta_t, +imin_plot, imax_plot, delta_i_plot) + print(to_print) + + + pdf_eta, values_inc_eta, nb_rx_to_plot = self.load_pdf_from_file( + tmin=tmin, tmax=tmax, key_var='eta') + + pdf_ux, values_inc_ux, nb_rx_to_plot = self.load_pdf_from_file( + tmin=tmin, tmax=tmax, key_var='ux') + + pdf_uy, values_inc_uy, nb_rx_to_plot = self.load_pdf_from_file( + tmin=tmin, tmax=tmax, key_var='uy') + + + # iorder = self.iorder_from_order(order) + order = float(order) + + x_left_axe = 0.12 + z_bottom_axe = 0.56 + width_axe = 0.85 + height_axe = 0.37 + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('$r_x$') + ax1.set_ylabel('$\langle \delta u^{'+'{0}'.format(order)+'} \\rangle$') + + + ax1.set_title('struct. functions, solver '+self.output.name_solver+ + ', nh = {0:5d}'.format(self.nx)) +# +', c = {0:.4g}, f = {1:.4g}'.format(np.sqrt(self.c2), self.f)) + ax1.hold(True) + ax1.set_xscale('log') + ax1.set_yscale(yscale) + + + + + + + + # So_eta = self.strfunc_from_pdf(pdf_eta, values_inc_eta, order) + So_ux = self.strfunc_from_pdf(pdf_ux, values_inc_ux, order) + So_uy = self.strfunc_from_pdf(pdf_uy, values_inc_uy, order) + + norm = rxs + + # ax1.set_ylabel('struct. functions, order = {0}'.format(order)) + # if delta_t != 0.: + # for it in xrange(imin_plot,imax_plot+1,delta_i_plot): + # struc_func_ux = dset_struc_func_ux[it] + # struc_func_ux = struc_func_ux.reshape( + # [self.norders, self.nrx]) + # struc_func_uy = dset_struc_func_uy[it] + # struc_func_uy = struc_func_uy.reshape( + # [self.norders, self.nrx]) + + # ax1.plot(rxs, struc_func_ux[iorder], 'c', linewidth=1) + # ax1.plot(rxs, struc_func_uy[iorder], 'm', linewidth=1) + + + + # struc_func_ux = dset_struc_func_ux[imin_plot:imax_plot+1].mean(0) + # struc_func_ux = struc_func_ux.reshape([self.norders, self.nrx]) + # struc_func_uy = dset_struc_func_uy[imin_plot:imax_plot+1].mean(0) + # struc_func_uy = struc_func_uy.reshape([self.norders, self.nrx]) + + # ax1.plot(rxs, struc_func_ux[iorder]/norm, 'c', linewidth=2) + # ax1.plot(rxs, struc_func_uy[iorder]/norm, 'm', linewidth=2) + + ax1.plot(rxs, So_ux/norm, 'c-.', linewidth=2) + ax1.plot(rxs, So_uy/norm, 'm-.', linewidth=2) + if order % 2 == 1: + ax1.plot(rxs, -So_ux/norm, 'c:', linewidth=2) + ax1.plot(rxs, -So_uy/norm, 'm:', linewidth=2) + + # ax1.plot(rxs, abs(struc_func_ux[iorder])/abs(struc_func_uy[iorder]), + # 'k', linewidth=1) + + ax1.plot(rxs, abs(So_ux)/abs(So_uy), + 'k', linewidth=1) + + # if self.orders[iorder]%2 == 1: + # ax1.plot(rxs, -struc_func_ux[iorder]/norm, '--b', linewidth=2) + # ax1.plot(rxs, -struc_func_uy[iorder]/norm, '--m', linewidth=2) + + cond = rxs < 6*deltax + ax1.plot(rxs[cond], 1.e4*rxs[cond]**(order)/norm[cond], + 'k', linewidth=2) + ax1.plot(rxs, rxs**(order/3)/norm, '--k', linewidth=2) + + ax1.plot(rxs, 1.e0*rxs**(1)/norm, ':k', linewidth=2) + + + z_bottom_axe = 0.09 + size_axe[1] = z_bottom_axe + ax2 = fig.add_axes(size_axe) + + + ax2.set_xlabel('$r_x$') + ax2.set_ylabel('flatness') + ax2.set_xscale('log') + ax2.set_yscale('log') + + # iorder4 = self.iorder_from_order(4) + # iorder2 = self.iorder_from_order(2) + + # if delta_t != 0.: + # for it in xrange(imin_plot,imax_plot+1,delta_i_plot): + # struc_func_ux = dset_struc_func_ux[it] + # struc_func_ux = struc_func_ux.reshape( + # [self.norders, self.nrx]) + # struc_func_uy = dset_struc_func_uy[it] + # struc_func_uy = struc_func_uy.reshape( + # [self.norders, self.nrx]) + + # flatnessL = struc_func_ux[iorder4]/struc_func_ux[iorder2]**2 + # flatnessT = struc_func_uy[iorder4]/struc_func_uy[iorder2]**2 + + # ax2.plot(rxs, flatnessL, 'c', linewidth=1) + # ax2.plot(rxs, flatnessT, 'm', linewidth=1) + + # struc_func_ux = dset_struc_func_ux[imin_plot:imax_plot+1].mean(0) + # struc_func_ux = struc_func_ux.reshape([self.norders, self.nrx]) + # struc_func_uy = dset_struc_func_uy[imin_plot:imax_plot+1].mean(0) + # struc_func_uy = struc_func_uy.reshape([self.norders, self.nrx]) + + # flatnessL = struc_func_ux[iorder4]/struc_func_ux[iorder2]**2 + # flatnessT = struc_func_uy[iorder4]/struc_func_uy[iorder2]**2 + # ax2.plot(rxs, flatnessL, 'c', linewidth=2) + # ax2.plot(rxs, flatnessT, 'm', linewidth=2) + + + S2_eta = self.strfunc_from_pdf(pdf_eta, values_inc_eta, 2) + S2_ux = self.strfunc_from_pdf(pdf_ux, values_inc_ux, 2) + S2_uy = self.strfunc_from_pdf(pdf_uy, values_inc_uy, 2) + + S4_eta = self.strfunc_from_pdf(pdf_eta, values_inc_eta, 4) + S4_ux = self.strfunc_from_pdf(pdf_ux, values_inc_ux, 4) + S4_uy = self.strfunc_from_pdf(pdf_uy, values_inc_uy, 4) + + flatnessL_bis = S4_ux/S2_ux**2 + flatnessT_bis = S4_uy/S2_uy**2 + flatness_eta = S4_eta/S2_eta**2 + + ax2.plot(rxs, flatnessL_bis, 'c--', linewidth=2) + ax2.plot(rxs, flatnessT_bis, 'm--', linewidth=2) + ax2.plot(rxs, flatness_eta, 'y--', linewidth=2) + + cond = np.logical_and(rxs < 70*deltax, + rxs > 5*deltax) + ax2.plot(rxs[cond], 1e1*rxs[cond]**(-1), ':k', linewidth=2) + + ax2.plot(rxs, 3*np.ones(rxs.shape), 'k--', linewidth=0.5) + + + + + def plot_Kolmo(self, tmin=0, tmax=None): + """Plot quantities appearing in the Kolmogorov law.""" + f = h5py.File(self.path_file, 'r') + dset_times = f['times'] + times = dset_times[...] + + if tmax is None: + tmax = times.max() + + rxs = f['rxs'][...] + + oper = f['/info_simul/params/oper'] + nx = oper.attrs['nx'] + Lx = oper.attrs['Lx'] + deltax = Lx/nx + + rxs = np.array(rxs, dtype=np.float64)*deltax + + + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + + to_print = 'plot(tmin={0}, tmax={1})'.format( + tmin, tmax) + print(to_print) + + to_print = '''plot structure functions +tmin = {0:8.6g} ; tmax = {1:8.6g} +imin = {2:8d} ; imax = {3:8d}'''.format( +tmin_plot, tmax_plot, +imin_plot, imax_plot) + print(to_print) + + + # dset_struc_func_ux = f['struc_func_ux'] + # struc_func_ux = dset_struc_func_ux[imin_plot:imax_plot+1].mean(0) + # struc_func_ux = struc_func_ux.reshape([self.norders, self.nrx]) + # order = 3 + # iorder = self.iorder_from_order(order) + # S_ux3 = struc_func_ux[iorder] + + S_uL2JL = f['struc_func_uL2JL'][imin_plot:imax_plot+1].mean(0) + S_uT2JL = f['struc_func_uT2JL'][imin_plot:imax_plot+1].mean(0) + S_c2h2uL = f['struc_func_c2h2uL'][imin_plot:imax_plot+1].mean(0) + S_Kolmo = f['struc_func_Kolmo'][imin_plot:imax_plot+1].mean(0) + S_uT2uL = f['struc_func_uT2uL'][imin_plot:imax_plot+1].mean(0) + + S_Kolmo_theo = -4*rxs + + x_left_axe = 0.12 + z_bottom_axe = 0.56 + width_axe = 0.85 + height_axe = 0.37 + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('$r_x$') + title = ('struct. functions, solver '+self.output.name_solver+ +', nh = {0:5d}'.format(self.nx)+ +', c2 = {0:.4g}, f = {1:.4g}'.format(self.c2, self.f) +) + ax1.set_title(title) + ax1.hold(True) + ax1.set_xscale('log') + ax1.set_yscale('linear') + + ax1.set_ylabel('struct. functions') + + + ax1.plot(rxs, S_Kolmo/S_Kolmo_theo, 'k', linewidth=2) + ax1.plot(rxs, (S_uL2JL+S_uT2JL)/S_Kolmo_theo, 'r', linewidth=2) + + ax1.plot(rxs, S_c2h2uL/S_Kolmo_theo, 'b', linewidth=2) + + ax1.plot(rxs, S_uL2JL/S_Kolmo_theo, 'r--', linewidth=1) + ax1.plot(rxs, S_uT2JL/S_Kolmo_theo, 'r-.', linewidth=1) + + ax1.plot(rxs, + (S_uL2JL+S_uT2JL+S_c2h2uL)/S_Kolmo_theo, + 'y', linewidth=1) + + + + cond = rxs < 6*deltax + ax1.plot(rxs[cond], 1.e0*rxs[cond]**3/S_Kolmo_theo[cond], + 'k', linewidth=2) + + + ax1.plot(rxs, np.ones(rxs.shape), 'k:', linewidth=1) + + ax1.set_ylim([5e-2, 1.5]) + + + + z_bottom_axe = 0.09 + size_axe[1] = z_bottom_axe + ax2 = fig.add_axes(size_axe) + + ax2.set_xlabel('$r_x$') + ax2.set_ylabel('ratio S_ux3/S_uT2uL') + ax2.set_xscale('log') + ax2.set_yscale('linear') + + # ax2.plot(rxs, S_ux3/S_uT2uL, 'k', linewidth=2) + + ax2.plot(rxs, S_uL2JL/S_uT2JL, 'k--', linewidth=2) + + + ax2.plot(rxs, 3*np.ones(rxs.shape), 'k:', linewidth=1) + + + ax2.set_ylim([2, 5]) + + + + + + +if __name__=="__main__": + + from solveq2d import solveq2d + + import glob + + c = 20 + resol = 240*2**2 # 4 + + str_resol = repr(resol) + str_to_find_path = ( + '/scratch/augier/Results_SW1lw' + '/Pure_standing_waves_'+ + str_resol+'*/SE2D*c='+repr(c))+'_*' + print(str_to_find_path) + + paths_dir = glob.glob(str_to_find_path) + + sim = solveq2d.create_sim_plot_from_dir(paths_dir[0]) + + tmin = sim.output.spatial_means.first_saved_time() + tstatio = tmin + 4. + + + # sim.output.increments.plot(tmin=tmin, tmax=None, delta_t=0., + # order=4, yscale='log') + + + # sim.output.increments.plot_pdf(tmin=tmin, tmax=160.25, key_var='ux', + # order=4) + + sim.output.increments.plot_Kolmo(tmin=tmin) + + + solveq2d.show() diff --git a/fluidsim/base/output/phys_fields.py b/fluidsim/base/output/phys_fields.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9vdXRwdXQvcGh5c19maWVsZHMucHk= --- /dev/null +++ b/fluidsim/base/output/phys_fields.py @@ -0,0 +1,277 @@ +"""Physical fields output (:mod:`fluidsim.base.output.phys_fields`) +========================================================================= + +.. currentmodule:: fluidsim.base.output.phys_fields + +Provides: + +.. autoclass:: PhysFieldsBase + :members: + :private-members: + +""" + +import matplotlib.pyplot as plt +import numpy as np +import h5py +import os +import datetime + +from fluiddyn.util import mpi +from .base import SpecificOutput + + +class PhysFieldsBase(SpecificOutput): + """Manage the output of physical fields.""" + + _tag = 'phys_fields' + + @staticmethod + def _complete_params_with_default(params): + tag = 'phys_fields' + params.output.set_child(tag, + attribs={'field_to_plot': 'ux'}) + + params.output.periods_save.set_attrib(tag, 0) + params.output.periods_plot.set_attrib(tag, 0) + + def __init__(self, output): + params = output.sim.params + + super(PhysFieldsBase, self).__init__( + output, + period_save=params.output.periods_save.phys_fields, + period_plot=params.output.periods_plot.phys_fields) + + self.field_to_plot = params.output.phys_fields.field_to_plot + + if self.period_save == 0 and self.period_plot == 0: + return + + self.t_last_save = self.sim.time_stepping.t + + self.t_last_plot = self.sim.time_stepping.t + + def init_files(self, dico_arrays_1time=None): + pass + + def init_online_plot(self): + pass + + def online_save(self): + """Online save.""" + tsim = self.sim.time_stepping.t + if (tsim-self.t_last_save >= self.period_save): + self.t_last_save = tsim + self.save() + + def online_plot(self): + """Online plot.""" + tsim = self.sim.time_stepping.t + if (tsim-self.t_last_plot >= self.period_plot): + self.t_last_plot = tsim + itsim = self.sim.time_stepping.it + self.plot(numfig=itsim, + key_field=self.params.output.phys_fields.field_to_plot) + + def save(self, state_phys=None, params=None, time=None, + particular_attr=None): + if state_phys is None: + state_phys = self.sim.state.state_phys + if params is None: + params = self.params + if time is None: + time = self.sim.time_stepping.t + + path_run = self.output.path_run + + if mpi.rank == 0 and not os.path.exists(path_run): + os.mkdir(path_run) + + if mpi.rank == 0: + name_save = \ + 'state_phys_t={0:7.3f}.hd5'.format(time).replace(' ', '0') + path_file = path_run+'/'+name_save + to_print = 'save state_phys in file '+name_save + self.output.print_stdout(to_print) + + f = h5py.File(path_file, 'w') + f.attrs['date saving'] = str(datetime.datetime.now()) + f.attrs['name_solver'] = self.output.name_solver + f.attrs['name_run'] = self.output.name_run + if particular_attr is not None: + f.attrs['particular_attr'] = particular_attr + + self.sim.info.xml_to_hdf5(hdf5_parent=f) + gp_info = f['info_simul'] + gf_params = gp_info['params'] + gf_params.attrs['SAVE'] = True + gf_params.attrs['NEW_DIR_RESULTS'] = True + + group_state_phys = f.create_group("state_phys") + group_state_phys.attrs['what'] = 'obj state_phys for solveq2d' + group_state_phys.attrs['name_type_variables'] = ( + state_phys.name_type_variables) + group_state_phys.attrs['time'] = time + + for k in state_phys.keys: + field_loc = state_phys[k] + if mpi.nb_proc > 1: + field_seq = self.oper.gather_Xspace(field_loc) + else: + field_seq = field_loc + if mpi.rank == 0: + group_state_phys.create_dataset(k, data=field_seq) + + if mpi.rank == 0: + f.close() + + def plot(self, numfig=None, field=None, key_field=None, + QUIVER=True, vecx='ux', vecy='uy', FIELD_LOC=True, + nb_contours=20, type_plot='contourf'): + + x_left_axe = 0.08 + z_bottom_axe = 0.07 + width_axe = 0.97 + height_axe = 0.87 + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + + keys_state_phys = self.sim.info.solver.classes.State['keys_state_phys'] + keys_computable = self.sim.info.solver.classes.State['keys_computable'] + + if vecx not in keys_state_phys or vecy not in keys_state_phys: + QUIVER = False + + if field is None: + if key_field is None: + field_to_plot = self.params.output.phys_fields.field_to_plot + if (field_to_plot in keys_state_phys and + field_to_plot in keys_computable): + key_field = field_to_plot + else: + if 'q' in keys_state_phys: + key_field = 'q' + elif 'rot' in keys_state_phys: + key_field = 'rot' + else: + key_field = keys_state_phys[0] + + field_loc = self.sim.state(key_field) + else: + key_field = 'given field' + if FIELD_LOC: + field_loc = field + + if mpi.nb_proc > 1 and FIELD_LOC: + field = self.oper.gather_Xspace(field_loc) + else: + field = field_loc + + if mpi.rank == 0: + if numfig is None: + fig, ax = self.output.figure_axe(size_axe=size_axe) + else: + fig, ax = self.output.figure_axe(numfig=numfig, + size_axe=size_axe) + x_seq = self.oper.x_seq + y_seq = self.oper.y_seq + [XX_seq, YY_seq] = np.meshgrid(x_seq, y_seq) + + if type_plot == 'contourf': + contours = ax.contourf(x_seq, y_seq, field, + nb_contours, cmap=plt.cm.jet) + fig.colorbar(contours) + fig.contours = contours + elif type_plot == 'pcolor': + pc = ax.pcolormesh(x_seq, y_seq, field, + cmap=plt.cm.jet) + fig.colorbar(pc) + + if QUIVER: + if isinstance(vecx, str): + vecx_loc = self.sim.state(vecx) + if mpi.nb_proc > 1: + vecx = self.oper.gather_Xspace(vecx_loc) + else: + vecx = vecx_loc + if isinstance(vecy, str): + vecy_loc = self.sim.state(vecy) + if mpi.nb_proc > 1: + vecy = self.oper.gather_Xspace(vecy_loc) + else: + vecy = vecy_loc + pas_vector = np.round(self.oper.nx_seq/48) + if pas_vector < 1: + pas_vector = 1 + + if mpi.rank == 0: + ax.quiver(XX_seq[::pas_vector, ::pas_vector], + YY_seq[::pas_vector, ::pas_vector], + vecx[::pas_vector, ::pas_vector], + vecy[::pas_vector, ::pas_vector]) + + if mpi.rank == 0: + ax.set_xlabel('x') + ax.set_ylabel('y') + + title = (key_field + + ', t = {0:.3f}, '.format(self.sim.time_stepping.t) + + self.output.name_solver + + ', nh = {0:d}'.format(self.params.oper.nx)) + + if QUIVER: + title += r', max(|v|) = {0:.3f}'.format( + np.max(np.sqrt(vecx**2+vecy**2))) + + ax.set_title(title) + + fig.canvas.draw() + + +class PhysFieldsBase1D(PhysFieldsBase): + + def plot(self, numfig=None, field=None, key_field=None): + + # x_left_axe = 0.08 + # z_bottom_axe = 0.07 + # width_axe = 0.87 + # height_axe = 0.87 + # size_axe = [x_left_axe, z_bottom_axe, + # width_axe, height_axe] + + keys_state_phys = self.sim.info.solver.classes.State['keys_state_phys'] + keys_computable = self.sim.info.solver.classes.State['keys_computable'] + + if field is None: + if key_field is None: + field_to_plot = self.params.output.phys_fields.field_to_plot + if (field_to_plot in keys_state_phys and + field_to_plot in keys_computable): + key_field = field_to_plot + else: + if 'q' in keys_state_phys: + key_field = 'q' + elif 'rot' in keys_state_phys: + key_field = 'rot' + else: + key_field = keys_state_phys[0] + + field_loc = self.sim.state(key_field) + else: + key_field = 'given field' + + if mpi.nb_proc > 1: + field = self.oper.gather_Xspace(field_loc) + else: + field = field_loc + + if mpi.rank == 0: + if numfig is None: + fig, ax = self.output.figure_axe(size_axe=None) + else: + fig, ax = self.output.figure_axe(numfig=numfig, + size_axe=None) + xs = self.oper.xs + + ax.plot(xs, field) diff --git a/fluidsim/base/output/print_stdout.py b/fluidsim/base/output/print_stdout.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9vdXRwdXQvcHJpbnRfc3Rkb3V0LnB5 --- /dev/null +++ b/fluidsim/base/output/print_stdout.py @@ -0,0 +1,87 @@ + +from __future__ import print_function + +import os +# import numpy as np + +# from time import time + +from fluiddyn.util import mpi + + +class PrintStdOutBase(object): + """A :class:`PrintStdOutBase` object is used to print in both the + stdout and the stdout.txt file, and also to print simple info on + the current state of the simulation.""" + + _tag = 'print_stdout' + + @staticmethod + def _complete_params_with_default(params): + params.output.periods_print.set_attrib('print_stdout', 1.) + + def __init__(self, output): + sim = output.sim + params = sim.params + + self.output = output + self.sim = sim + self.params = params + + try: + self.c2 = params.c2 + self.f = params.f + except AttributeError: + pass + + self.nx = params.oper.nx + + self.period_print = params.output.periods_print.print_stdout + + self.path_file = self.output.path_run+'/stdout.txt' + + if mpi.rank == 0 and self.output.has_to_save: + if not os.path.exists(self.path_file): + self.file = open(self.path_file, 'w') + else: + self.file = open(self.path_file, 'r+') + self.file.seek(0, 2) # go to the end of the file + + def complete_init_with_state(self): + + self.energy0 = self.output.compute_energy() + + if self.period_print == 0: + return + + self.energy_temp = self.energy0+0. + self.t_last_print_info = -self.period_print + self.t_real_word_last = 0. + + self.print_stdout = self.__call__ + + def __call__(self, to_print, end='\n'): + """Print in stdout and if SAVE in the file stdout.txt""" + if mpi.rank == 0: + print(to_print, end=end) + if self.output.has_to_save: + self.file.write(to_print+end) + self.file.flush() + os.fsync(self.file.fileno()) + + def online_print(self): + """Print simple info on the current state of the simulation""" + tsim = self.sim.time_stepping.t + if (tsim-self.t_last_print_info >= self.period_print): + self.t_last_print_info = tsim + self.print_stdout( + 'it = {0:6d} ; t = {1:10.6g} ; deltat = {2:10.5g}\n'.format( + self.sim.time_stepping.it, + self.sim.time_stepping.t, + self.sim.time_stepping.deltat)) + + def close(self): + try: + self.file.close() + except AttributeError: + pass diff --git a/fluidsim/base/output/prob_dens_func.py b/fluidsim/base/output/prob_dens_func.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9vdXRwdXQvcHJvYl9kZW5zX2Z1bmMucHk= --- /dev/null +++ b/fluidsim/base/output/prob_dens_func.py @@ -0,0 +1,169 @@ + +import h5py +import numpy as np + +from fluidsim.base.output.base import SpecificOutput + + +class ProbaDensityFunc(SpecificOutput): + """A :class:`ProbaDensityFunc` object handles the saving of pdf. + """ + _tag = 'pdf' + + @staticmethod + def _complete_params_with_default(params): + tag = 'pdf' + + params.output.periods_save.set_attrib(tag, 0) + params.output.set_child(tag, + attribs={'HAS_TO_PLOT_SAVED': False}) + + def __init__(self, output): + params = output.sim.params + self.c2 = params.c2 + self.f = params.f + self.nx = params.oper.nx + + super(ProbaDensityFunc, self).__init__( + output, + name_file='pdf.h5', + period_save=params.output.periods_save.pdf, + has_to_plot_saved=params.output.pdf.HAS_TO_PLOT_SAVED) + + def init_online_plot(self): + self.fig, axe = self.output.figure_axe(numfig=5000000) + self.axe = axe + axe.set_xlabel('$\eta$') + axe.set_ylabel('pdf') + title = ('pdf $\eta$, solver ' + self.output.name_solver + + ', nh = {0:5d}'.format(self.nx) + + ', c = {0:.4g}, f = {1:.4g}'.format(np.sqrt(self.c2), self.f)) + axe.set_title(title) + axe.hold(True) + + def _online_plot(self, dico_pdf): + """online plot on pdf""" + pdf_eta = dico_pdf['pdf_eta'] + bin_edges_eta = dico_pdf['bin_edges_eta'] + self.axe.plot(bin_edges_eta[:-1], pdf_eta, 'k') + + def compute(self): + """compute the values at one time.""" + eta = self.sim.state.state_phys['eta'] + pdf_eta, bin_edges_eta = self.oper.pdf_normalized(eta) + + ux = self.sim.state.state_phys['ux'] + uy = self.sim.state.state_phys['uy'] + + uxm = ux.mean() + uym = uy.mean() + + u_norme = np.sqrt((ux - uxm)**2 + (uy - uym)**2) + pdf_u, bin_edges_u = self.oper.pdf_normalized(u_norme) + + dico_pdf = {'pdf_eta': pdf_eta, + 'bin_edges_eta': bin_edges_eta, + 'pdf_u': pdf_u, + 'bin_edges_u': bin_edges_u} + return dico_pdf + + def load(self): + """load the saved pdf and return a dictionary.""" + f = h5py.File(self.path_file, 'r') + # dset_times = f['times'] + # times = dset_times[...] + # nt = len(times) + + dset_pdf_eta = f['pdf_eta'] + dset_bin_edges_eta = f['bin_edges_eta'] + + pdf_eta = dset_pdf_eta[...] + bin_edges_eta = dset_bin_edges_eta[...] + + dset_pdf_u = f['pdf_u'] + dset_bin_edges_u = f['bin_edges_u'] + + pdf_u = dset_pdf_u[...] + bin_edges_u = dset_bin_edges_u[...] + + dico_pdf = {'pdf_eta': pdf_eta, + 'bin_edges_eta': bin_edges_eta, + 'pdf_u': pdf_u, + 'bin_edges_u': bin_edges_u} + return dico_pdf + + def plot(self, tmin=0, tmax=1000, delta_t=2): + """Plot some pdf.""" + f = h5py.File(self.path_file, 'r') + dset_times = f['times'] + times = dset_times[...] + # nt = len(times) + + dset_pdf_eta = f['pdf_eta'] + dset_bin_edges_eta = f['bin_edges_eta'] + dset_pdf_u = f['pdf_u'] + dset_bin_edges_u = f['bin_edges_u'] + + delta_t_save = np.mean(times[1:]-times[0:-1]) + delta_i_plot = int(np.round(delta_t/delta_t_save)) + if delta_i_plot == 0: + delta_i_plot = 1 + delta_t = delta_i_plot*delta_t_save + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + + to_print = 'plot(tmin={0}, tmax={1}, delta_t={2:.2f})'.format( + tmin, tmax, delta_t) + print(to_print) + + to_print = ('plot pdf eta\n' + 'tmin = {0:8.6g} ; tmax = {1:8.6g} ; delta_t = {2:8.6g}' + 'imin = {3:8d} ; imax = {4:8d} ; delta_i = {5:8d}').format( + tmin_plot, tmax_plot, delta_t, + imin_plot, imax_plot, delta_i_plot) + print(to_print) + + x_left_axe = 0.12 + z_bottom_axe = 0.56 + width_axe = 0.85 + height_axe = 0.37 + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('$\eta$') + ax1.set_ylabel('PDF') + ax1.set_title('PDF, solver ' + self.output.name_solver + + ', nh = {0:5d}'.format(self.nx) + + ', c = {0:.4g}, f = {1:.4g}'.format( + np.sqrt(self.c2), self.f)) + ax1.hold(True) + ax1.set_xscale('linear') + ax1.set_yscale('linear') + + for it in xrange(imin_plot, imax_plot+1, delta_i_plot): + pdf_eta = dset_pdf_eta[it] + bin_edges_eta = dset_bin_edges_eta[it] + + bin_edges_eta = (bin_edges_eta[:-1]+bin_edges_eta[1:])/2 + ax1.plot(bin_edges_eta, pdf_eta, 'c', linewidth=1) + + z_bottom_axe = 0.09 + size_axe[1] = z_bottom_axe + ax2 = fig.add_axes(size_axe) + + ax2.set_xlabel('$ |\\mathbf{u}-\\langle \\mathbf{u} \\rangle | $') + ax2.set_ylabel('PDF') + ax2.hold(True) + ax2.set_xscale('linear') + ax2.set_yscale('linear') + + for it in xrange(imin_plot, imax_plot+1, delta_i_plot): + pdf_u = dset_pdf_u[it] + bin_edges_u = dset_bin_edges_u[it] + + bin_edges_u = (bin_edges_u[:-1]+bin_edges_u[1:])/2 + ax2.plot(bin_edges_u, pdf_u, 'r', linewidth=1) diff --git a/fluidsim/base/output/spatial_means.py b/fluidsim/base/output/spatial_means.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9vdXRwdXQvc3BhdGlhbF9tZWFucy5weQ== --- /dev/null +++ b/fluidsim/base/output/spatial_means.py @@ -0,0 +1,157 @@ + +from __future__ import division, print_function + +import os +import numpy as np + + +from fluiddyn.util import mpi + +from .base import SpecificOutput + + +def inner_prod(a_fft, b_fft): + return np.real(a_fft.conj()*b_fft) + + +class SpatialMeansBase(SpecificOutput): + """A :class:`SpatialMean` object handles the saving of . + + This class uses the particular functions defined by some solvers + :func:`` and + :func``. If the solver doesn't has these + functions, this class does nothing. + """ + + _tag = 'spatial_means' + + @staticmethod + def _complete_params_with_default(params): + tag = 'spatial_means' + + params.output.periods_save.set_attrib(tag, 0) + params.output.set_child(tag, + attribs={'HAS_TO_PLOT_SAVED': False}) + + def __init__(self, output): + params = output.sim.params + self.nx = params.oper.nx + + self.sum_wavenumbers = output.sum_wavenumbers + self.vecfft_from_rotfft = output.vecfft_from_rotfft + + super(SpatialMeansBase, self).__init__( + output, + period_save=params.output.periods_save.spatial_means, + has_to_plot_saved=params.output.spatial_means.HAS_TO_PLOT_SAVED) + + if self.period_save != 0: + self.save_one_time() + + def init_path_files(self): + self.path_file = self.output.path_run + '/spatial_means.txt' + + def init_files(self, dico_arrays_1time=None): + + if mpi.rank == 0: + if not os.path.exists(self.path_file): + self.file = open(self.path_file, 'w') + else: + self.file = open(self.path_file, 'r+') + # to go to the end of the file + self.file.seek(0, 2) + + def online_save(self): + self() + + def __call__(self): + """Save the values at one time. """ + if (self.sim.time_stepping.t-self.t_last_save >= self.period_save): + self.t_last_save = self.sim.time_stepping.t + self.save_one_time() + + def save_one_time(self): + self.t_last_save = self.sim.time_stepping.t + + def init_online_plot(self): + if mpi.rank == 0: + width_axe = 0.85 + height_axe = 0.4 + x_left_axe = 0.12 + z_bottom_axe = 0.55 + + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + fig, axe = self.output.figure_axe(size_axe=size_axe, + numfig=3000000) + self.axe_a = axe + axe.set_xlabel('$t$') + axe.set_ylabel('$E(t)$') + title = ('mean quantities, solver ' + self.output.name_solver + + ', nh = {0:5d}'.format(self.nx)) + axe.set_title(title) + axe.hold(True) + + z_bottom_axe = 0.08 + size_axe[1] = z_bottom_axe + axe = fig.add_axes(size_axe) + self.axe_b = axe + axe.set_xlabel('$t$') + axe.set_ylabel('$\epsilon(t)$') + axe.hold(True) + + def load(self): + dico_results = {} + return dico_results + + def plot(self): + pass + + def compute_time_means(self, tstatio=0., tmax=None): + """compute the temporal means.""" + dico_results = self.load() + if tmax is None: + times = dico_results['t'] + imax_mean = times.size-1 + tmax = times[imax_mean] + else: + imax_mean = np.argmin(abs(times-tmax)) + imin_mean = np.argmin(abs(times-tstatio)) + + dico_time_means = {} + for key, value in dico_results.iteritems(): + if isinstance(value, np.ndarray): + dico_time_means[key] = np.mean( + value[imin_mean:imax_mean+1] + ) + return dico_time_means, dico_results + + def close_file(self): + try: + self.file.close() + except AttributeError: + pass + + def time_first_saved(self): + file_means = open(self.path_file) + line = '' + while not line.startswith('time ='): + line = file_means.readline() + file_means.close() + words = line.split() + return float(words[2]) + + def time_last_saved(self): + file_means = open(self.path_file) + file_means.seek(0, 2) # go to the end + nb_caract = file_means.tell() + nb_caract_to_read = min(nb_caract, 1000) + file_means.seek(-nb_caract_to_read, 2) + line = file_means.readline() + while line != '': + if line.startswith('time ='): + line_time = line + line = file_means.readline() + file_means.close() + words = line_time.split() + return float(words[2]) diff --git a/fluidsim/base/output/spect_energy_budget.py b/fluidsim/base/output/spect_energy_budget.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9vdXRwdXQvc3BlY3RfZW5lcmd5X2J1ZGdldC5weQ== --- /dev/null +++ b/fluidsim/base/output/spect_energy_budget.py @@ -0,0 +1,113 @@ + +import numpy as np + +from fluiddyn.util import mpi + +from .base import SpecificOutput + + +def cumsum_inv(a): + return a[::-1].cumsum()[::-1] + + +def inner_prod(a_fft, b_fft): + return np.real(a_fft.conj()*b_fft) + + +class SpectralEnergyBudgetBase(SpecificOutput): + """A :class:`Spectra` object handles the saving of . + + This class uses the particular functions defined by some solvers + :func:`` and + :func``. If the solver doesn't has these + functions, this class does nothing. + """ + + _tag = 'spect_energy_budg' + + @staticmethod + def _complete_params_with_default(params): + tag = 'spect_energy_budg' + + params.output.periods_save.set_attrib(tag, 0) + params.output.set_child(tag, + attribs={'HAS_TO_PLOT_SAVED': False}) + + def __init__(self, output): + + params = output.sim.params + self.nx = params.oper.nx + + self.spectrum2D_from_fft = output.sim.oper.spectrum2D_from_fft + + HAS_TO_PLOT_SAVED = params.output.spect_energy_budg.HAS_TO_PLOT_SAVED + super(SpectralEnergyBudgetBase, self).__init__( + output, + name_file='spectral_energy_budget.h5', + period_save=params.output.periods_save.spect_energy_budg, + has_to_plot_saved=HAS_TO_PLOT_SAVED, + dico_arrays_1time={'khE': output.sim.oper.khE}) + + def compute(self): + """compute the values at one time.""" + if mpi.rank == 0: + dico_results = {} + return dico_results + + def init_online_plot(self): + width_axe = 0.85 + height_axe = 0.37 + x_left_axe = 0.12 + z_bottom_axe = 0.56 + + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + self.fig, axe_a = self.output.figure_axe(size_axe=size_axe, + numfig=4000000) + self.axe_a = axe_a + axe_a.set_xlabel('k_h') + axe_a.set_ylabel('Pi(k_h) energy') + axe_a.set_title('energy flux, solver ' + self.output.name_solver + + ', nh = {0:5d}'.format(self.nx)) + axe_a.hold(True) + axe_a.set_xscale('log') + + z_bottom_axe = 0.08 + size_axe[1] = z_bottom_axe + axe_b = self.fig.add_axes(size_axe) + self.axe_b = axe_b + axe_b.set_xlabel('k_h') + axe_b.set_ylabel('Pi(k_h) energy') + axe_b.hold(True) + axe_b.set_xscale('log') + + def fnonlinfft_from_uxuy_funcfft(self, ux, uy, f_fft): + """Compute a non-linear term.""" + oper = self.oper + px_f_fft, py_f_fft = oper.gradfft_from_fft(f_fft) + px_f = oper.ifft2(px_f_fft) + py_f = oper.ifft2(py_f_fft) + del(px_f_fft, py_f_fft) + Fnl = -ux*px_f - uy*py_f + del(px_f, py_f) + Fnl_fft = oper.fft2(Fnl) + oper.dealiasing(Fnl_fft) + return Fnl_fft + + def fnonlinfft_from_uruddivfunc(self, + urx, ury, + udx, udy, div, + func_fft, func): + """Compute a non-linear term.""" + oper = self.oper + px_func_fft, py_func_fft = oper.gradfft_from_fft(func_fft) + px_func = oper.ifft2(px_func_fft) + py_func = oper.ifft2(py_func_fft) + del(px_func_fft, py_func_fft) + Frf = -urx*px_func - ury*py_func + Fdf = -udx*px_func - udy*py_func - div*func/2 + del(px_func, py_func) + Frf_fft = oper.fft2(Frf) + Fdf_fft = oper.fft2(Fdf) + oper.dealiasing(Frf_fft, Fdf_fft) + return Frf_fft, Fdf_fft diff --git a/fluidsim/base/output/spectra.py b/fluidsim/base/output/spectra.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9vdXRwdXQvc3BlY3RyYS5weQ== --- /dev/null +++ b/fluidsim/base/output/spectra.py @@ -0,0 +1,183 @@ +import h5py + +import os +import numpy as np + +from fluiddyn.util import mpi + + +from .base import SpecificOutput + + +class Spectra(SpecificOutput): + """Used for the saving of spectra. + + + """ + + _tag = 'spectra' + + @staticmethod + def _complete_params_with_default(params): + tag = 'spectra' + + params.output.periods_save.set_attrib(tag, 0) + params.output.set_child(tag, + attribs={'HAS_TO_PLOT_SAVED': False}) + + def __init__(self, output): + params = output.sim.params + self.nx = params.oper.nx + + self.spectrum2D_from_fft = output.sim.oper.spectrum2D_from_fft + self.spectra1D_from_fft = output.sim.oper.spectra1D_from_fft + + super(Spectra, self).__init__( + output, + period_save=params.output.periods_save.spectra, + has_to_plot_saved=params.output.spectra.HAS_TO_PLOT_SAVED) + + def init_path_files(self): + path_run = self.output.path_run + self.path_file1D = path_run + '/spectra1D.h5' + self.path_file2D = path_run + '/spectra2D.h5' + + def init_files(self, dico_arrays_1time=None): + dico_spectra1D, dico_spectra2D = self.compute() + if mpi.rank == 0: + if not os.path.exists(self.path_file1D): + dico_arrays_1time = {'kxE': self.sim.oper.kxE, + 'kyE': self.sim.oper.kyE} + self.create_file_from_dico_arrays( + self.path_file1D, dico_spectra1D, dico_arrays_1time) + dico_arrays_1time = {'khE': self.sim.oper.khE} + self.create_file_from_dico_arrays( + self.path_file2D, dico_spectra2D, dico_arrays_1time) + self.nb_saved_times = 1 + else: + with h5py.File(self.path_file1D, 'r') as f: + dset_times = f['times'] + self.nb_saved_times = dset_times.shape[0]+1 + # save the spectra in the file spectra1D.h5 + self.add_dico_arrays_to_file(self.path_file1D, + dico_spectra1D) + # save the spectra in the file spectra2D.h5 + self.add_dico_arrays_to_file(self.path_file2D, + dico_spectra2D) + + self.t_last_save = self.sim.time_stepping.t + + def online_save(self): + """Save the values at one time. """ + tsim = self.sim.time_stepping.t + if (tsim-self.t_last_save >= self.period_save): + self.t_last_save = tsim + dico_spectra1D, dico_spectra2D = self.compute() + if mpi.rank == 0: + # save the spectra in the file spectra1D.h5 + self.add_dico_arrays_to_file(self.path_file1D, + dico_spectra1D) + # save the spectra in the file spectra2D.h5 + self.add_dico_arrays_to_file(self.path_file2D, + dico_spectra2D) + self.nb_saved_times += 1 + if self.has_to_plot: + self._online_plot(dico_spectra1D, dico_spectra2D) + + if (tsim-self.t_last_show >= self.period_show): + self.t_last_show = tsim + self.axe.get_figure().canvas.draw() + + def compute(self): + """compute the values at one time.""" + if mpi.rank == 0: + dico_results = {} + return dico_results + + def init_online_plot(self): + fig, axe = self.output.figure_axe(numfig=1000000) + self.axe = axe + axe.set_xlabel('$k_h$') + axe.set_ylabel('$E(k_h)$') + axe.set_title('spectra, solver '+self.output.name_solver + + ', nh = {0:5d}'.format(self.nx)) + axe.hold(True) + + def _online_plot(self): + pass + + def load2D_mean(self, tmin=None, tmax=None): + f = h5py.File(self.path_file2D, 'r') + dset_times = f['times'] + times = dset_times[...] + nt = len(times) + + kh = f['khE'][...] + + if tmin is None: + imin_plot = 0 + else: + imin_plot = np.argmin(abs(times-tmin)) + + if tmax is None: + imax_plot = nt-1 + else: + imax_plot = np.argmin(abs(times-tmax)) + + tmin = times[imin_plot] + tmax = times[imax_plot] + + print('compute mean of 2D spectra\n' + ('tmin = {0:8.6g} ; tmax = {1:8.6g}' + 'imin = {2:8d} ; imax = {3:8d}').format( + tmin, tmax, imin_plot, imax_plot)) + + dico_results = {'kh': kh} + for key in f.keys(): + if key.startswith('spectr'): + dset_key = f[key] + spect = dset_key[imin_plot:imax_plot+1].mean(0) + dico_results[key] = spect + return dico_results + + def load1D_mean(self, tmin=None, tmax=None): + f = h5py.File(self.path_file1D, 'r') + dset_times = f['times'] + times = dset_times[...] + nt = len(times) + + kx = f['kxE'][...] + # ky = f['kyE'][...] + kh = kx + + if tmin is None: + imin_plot = 0 + else: + imin_plot = np.argmin(abs(times-tmin)) + + if tmax is None: + imax_plot = nt-1 + else: + imax_plot = np.argmin(abs(times-tmax)) + + tmin = times[imin_plot] + tmax = times[imax_plot] + + print('compute mean of 1D spectra' + ('tmin = {0:8.6g} ; tmax = {1:8.6g}\n' + 'imin = {2:8d} ; imax = {3:8d}\n').format( + tmin, tmax, imin_plot, imax_plot)) + + dico_results = {'kh': kh} + for key in f.keys(): + if key.startswith('spectr'): + dset_key = f[key] + spect = dset_key[imin_plot:imax_plot+1].mean(0) + dico_results[key] = spect + return dico_results + + def plot1D(self): + pass + + def plot2D(self): + pass diff --git a/fluidsim/base/output/time_signalsK.py b/fluidsim/base/output/time_signalsK.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9vdXRwdXQvdGltZV9zaWduYWxzSy5weQ== --- /dev/null +++ b/fluidsim/base/output/time_signalsK.py @@ -0,0 +1,474 @@ +import h5py + +import os +import numpy as np + +from fluiddyn.util import mpi + +from fluidsim.base.output.base import SpecificOutput + +from fluidsim.operators.fft import easypyfft + + +class TimeSignalsK(SpecificOutput): + """A :class:`TimeSignalK` object handles the saving of time signals + in spectral space. + + This class uses the particular functions defined by some solvers + :func:`linear_eigenmode_from_values_1k` and + :func`omega_from_wavenumber`. + """ + + _tag = 'time_signals_fft' + + @staticmethod + def _complete_params_with_default(params): + tag = 'time_signals_fft' + + params.output.periods_save.set_attrib(tag, 0) + params.output.periods_plot.set_attrib(tag, 0) + + params.output.set_child(tag, + attribs={'nb_shells_time_sigK': 4, + 'nb_k_per_shell_time_sigK': 4}) + + def __init__(self, output): + self.output = output + sim = output.sim + params = sim.params + + self.params = params + self.c2 = params.c2 + self.f = params.f + self.nx = params.oper.nx + + if not params.output.HAS_TO_SAVE: + params.output.periods_save.time_signals_fft = False + + if params.output.periods_save.time_signals_fft: + self._init_save(sim) + + super(TimeSignalsK, self).__init__( + output, + period_save=params.output.periods_save.time_signals_fft, + period_plot=params.output.periods_plot.time_signals_fft) + + def _init_save(self, sim): + params = self.params + self.nb_shells = params.output.time_signals_fft.nb_shells_time_sigK + self.nb_k_per_shell = \ + params.output.time_signals_fft.nb_k_per_shell_time_sigK + self.nb_k_tot = self.nb_shells*self.nb_k_per_shell + + i_shift = 3 + deltalogk = np.log( + params.oper.nx / 2 * + params.oper.coef_dealiasing)/(self.nb_shells+i_shift) + + deltakh = sim.oper.deltakh + + self.kh_shell = deltakh*np.exp( + deltalogk*np.arange(i_shift, self.nb_shells+i_shift)) + + self.kh_shell = deltakh*np.round(self.kh_shell/deltakh) + + for i_s in xrange(1, self.nb_shells): + if self.kh_shell[i_s-1] == self.kh_shell[i_s]: + self.kh_shell[i_s] += deltakh + + # if mpi.rank == 0: + # print 'self.kh_shell/deltakh' + # print self.kh_shell/sim.oper.deltakh + + # hypothese dispersion relation only function of the module + # of the wavenumber ("shells") + self.omega_shell = self.output.omega_from_wavenumber(self.kh_shell) + + kx_array_ik_approx = np.empty([self.nb_k_tot]) + ky_array_ik_approx = np.empty([self.nb_k_tot]) + + delta_angle = np.pi/(self.nb_k_per_shell-1) + for ishell, kh_s in enumerate(self.kh_shell): + angle = -np.pi/2 + for ikps in xrange(self.nb_k_per_shell): + kx_array_ik_approx[ishell*self.nb_shells+ikps] = \ + kh_s*np.cos(angle) + ky_array_ik_approx[ishell*self.nb_shells+ikps] = \ + kh_s*np.sin(angle) + angle += delta_angle + + self.ik0_array_ik = np.empty([self.nb_k_tot], dtype=np.int32) + self.ik1_array_ik = np.empty([self.nb_k_tot], dtype=np.int32) + if mpi.nb_proc > 1: + self.rank_array_ik = np.empty([self.nb_k_tot], dtype=np.int32) + + for ik in xrange(self.nb_k_tot): + kx_approx = kx_array_ik_approx[ik] + ky_approx = ky_array_ik_approx[ik] + rank_ik, ik0, ik1 = \ + sim.oper.where_is_wavenumber(kx_approx, ky_approx) + if mpi.nb_proc > 1: + self.rank_array_ik[ik] = rank_ik + self.ik0_array_ik[ik] = ik0 + self.ik1_array_ik[ik] = ik1 + + self.kx_array_ik = np.empty([self.nb_k_tot]) + self.ky_array_ik = np.empty([self.nb_k_tot]) + + for ik in xrange(self.nb_k_tot): + ik0_ik = self.ik0_array_ik[ik] + ik1_ik = self.ik1_array_ik[ik] + + if mpi.nb_proc > 1: + rank_ik = self.rank_array_ik[ik] + else: + rank_ik = 0 + + if mpi.rank == rank_ik: + kx_1k = sim.oper.KX[ik0_ik, ik1_ik] + ky_1k = sim.oper.KY[ik0_ik, ik1_ik] + + if rank_ik != 0: + if mpi.rank == rank_ik: + data = np.array([kx_1k, ky_1k]) + mpi.comm.Send( + [data, mpi.MPI.DOUBLE], + dest=0, tag=ik) + elif mpi.rank == 0: + data = np.empty([2], np.float64) + mpi.comm.Recv( + [data, mpi.MPI.DOUBLE], + source=rank_ik, tag=ik) + kx_1k = data[0] + ky_1k = data[1] + + if mpi.rank == 0: + self.kx_array_ik[ik] = kx_1k + self.ky_array_ik[ik] = ky_1k + + if mpi.rank == 0: + self.kh_array_ik = np.sqrt(self.kx_array_ik**2 + + self.ky_array_ik**2) + + self.omega_array_ik = self.output.omega_from_wavenumber( + self.kh_array_ik) + + self.period_save = np.pi/(8*self.omega_array_ik.max()) + else: + self.period_save = 0. + + if mpi.nb_proc > 1: + self.period_save = mpi.comm.bcast(self.period_save) + + def init_path_files(self): + self.path_file = self.output.path_run + '/time_sigK.h5' + + def init_files(self, dico_arrays_1time=None): + if (not os.path.exists(self.path_file)): + dico_results = self.compute() + if mpi.rank == 0: + dico_arrays_1time = { + 'kh_shell': self.kh_shell, + 'omega_shell': self.omega_shell, + 'kx_array_ik': self.kx_array_ik, + 'ky_array_ik': self.ky_array_ik, + 'kh_array_ik': self.kh_array_ik, + 'omega_array_ik': self.omega_array_ik} + self.create_file_from_dico_arrays( + self.path_file, dico_results, dico_arrays_1time) + + if mpi.rank == 0: + self.file = h5py.File(self.path_file, 'r+') + self.file.attrs['nb_shells'] = self.nb_shells + self.file.attrs['nb_k_per_shell'] = self.nb_k_per_shell + self.file.attrs['nb_k_tot'] = self.nb_k_tot + # the file is kept open during all the simulation + self.nb_saved_times = 1 + + self.t_last_save = self.sim.time_stepping.t + + def online_save(self): + """Save the values at one time. """ + tsim = self.sim.time_stepping.t + if (tsim-self.t_last_save >= self.period_save): + self.t_last_save = tsim + dico_results = self.compute() + if mpi.rank == 0: + self.add_dico_arrays_to_open_file(self.file, + dico_results, + self.nb_saved_times) + self.nb_saved_times += 1 + + def compute(self): + """compute the values at one time.""" + + ux_fft = self.sim.state('ux_fft') + uy_fft = self.sim.state('uy_fft') + eta_fft = self.sim.state('eta_fft') + + if mpi.rank == 0: + q_array_ik = np.empty([self.nb_k_tot], dtype=np.complex128) + d_array_ik = np.empty([self.nb_k_tot], dtype=np.complex128) + a_array_ik = np.empty([self.nb_k_tot], dtype=np.complex128) + + for ik in xrange(self.nb_k_tot): + ik0_ik = self.ik0_array_ik[ik] + ik1_ik = self.ik1_array_ik[ik] + + if mpi.rank == 0: + kx_ik = self.kx_array_ik[ik] + ky_ik = self.ky_array_ik[ik] + + if mpi.nb_proc > 1: + rank_ik = self.rank_array_ik[ik] + else: + rank_ik = 0 + + if mpi.rank == rank_ik: + ux_1k = ux_fft[ik0_ik, ik1_ik] + uy_1k = uy_fft[ik0_ik, ik1_ik] + eta_1k = eta_fft[ik0_ik, ik1_ik] + + if rank_ik != 0: + if mpi.rank == rank_ik: + data = np.array([ux_1k, uy_1k, eta_1k]) + mpi.comm.Send( + [data, mpi.MPI.COMPLEX], + dest=0, tag=ik) + elif mpi.rank == 0: + data = np.empty([3], np.complex128) + mpi.comm.Recv( + [data, mpi.MPI.COMPLEX], + source=rank_ik, tag=ik) + ux_1k = data[0] + uy_1k = data[1] + eta_1k = data[2] + + if mpi.rank == 0: + q_1k, d_1k, a_1k = ( + self.output.linear_eigenmode_from_values_1k( + ux_1k, uy_1k, eta_1k, kx_ik, ky_ik)) + q_array_ik[ik] = q_1k + d_array_ik[ik] = d_1k + a_array_ik[ik] = a_1k + + if mpi.rank == 0: + dico_results = {'q_array_ik': q_array_ik, + 'd_array_ik': d_array_ik, + 'a_array_ik': a_array_ik} + return dico_results + + def load(self): + + if (not os.path.exists(self.path_file)): + raise ValueError( + 'no file time_sigK.h5 in\n'+self.output.dir_save_run) + + with h5py.File(self.path_file, 'r+') as f: + + dset_times = f['times'] + times = dset_times[...] + + dico_results = {} + dico_results['times'] = times + + dico_results['nb_shells'] = f.attrs['nb_shells'] + dico_results['nb_k_per_shell'] = f.attrs['nb_k_per_shell'] + dico_results['nb_k_tot'] = f.attrs['nb_k_tot'] + + keys_1time = [ + 'kh_shell', + 'omega_shell', + 'kx_array_ik', + 'ky_array_ik', + 'kh_array_ik', + 'omega_array_ik'] + + for key in keys_1time: + dset_temp = f[key] + dico_results[key] = dset_temp[...] + + keys_linear_eigenmodes = \ + self.sim.info.solver.classes.State.keys_linear_eigenmodes + + for key in keys_linear_eigenmodes: + dset_temp = f[key[:-3]+'array_ik'] + A = dset_temp[...] + dico_results['sig_'+key] = np.ascontiguousarray(A.transpose()) + return dico_results + + def plot(self): + dico_results = self.load() + + t = dico_results['times'] + + nb_shells = dico_results['nb_shells'] + nb_k_per_shell = dico_results['nb_k_per_shell'] + + sig_q_fft = dico_results['sig_q_fft'] + sig_a_fft = dico_results['sig_a_fft'] + sig_d_fft = dico_results['sig_d_fft'] + + kh_shell = dico_results['kh_shell'] + omega_shell = dico_results['omega_shell'] + period_shell = 2*np.pi/omega_shell + + for ish in xrange(nb_shells): + + fig, ax1 = self.output.figure_axe() + ax1.set_xlabel('$t/T$') + ax1.set_ylabel('signals (s$^{-1}$)') + title = ( + 'signals eigenmodes, ikh = {0:.2f}, solver '.format( + kh_shell[ish]/sim.oper.deltakh) + + self.output.name_solver + + ', nh = {0:5d}'.format(self.nx) + + ', c2 = {0:.4g}, f = {1:.4g}'.format(self.c2, self.f) + ) + ax1.set_title(title) + ax1.hold(True) + + coef_norm_a = self.c2/omega_shell[ish] + + T = period_shell[ish] + + for ikps in xrange(nb_k_per_shell): + isig = ish*nb_k_per_shell+ikps + + ax1.plot(t/T, sig_q_fft[isig].real, 'k', linewidth=1) + ax1.plot(t/T, coef_norm_a*sig_a_fft[isig].real, + 'c', linewidth=1) + ax1.plot(t/T, sig_d_fft[isig].real, 'y', linewidth=1) + + fig, ax1 = self.output.figure_axe() + ax1.set_xlabel('$\omega$') + ax1.set_ylabel('kh_shell') + ax1.loglog(kh_shell, omega_shell, 'o', linewidth=2) + + def time_spectrum(self, sig_long): + + Nt = sig_long.size + stepit0 = int(np.fix(self.nt/2)) + + nb_spectra = 0 + it0 = 0 + spect = np.zeros([self.nt/2+1]) + while it0+self.nt < Nt: + nb_spectra += 1 + sig = sig_long[it0:it0+self.nt] + spect_raw = ( + abs(self.opfft1d.fft(self.hann*sig))**2 / 2 / self.deltaomega) + spect += spect_raw[:self.nt/2+1] + if self.nt % 2 == 0: + spect[1:self.nt/2] += spect_raw[self.nt-1:self.nt/2:-1] + else: + spect[1:self.nt/2+1] += spect_raw[self.nt-1:self.nt/2:-1] + it0 += stepit0 + + return spect/nb_spectra + + def compute_spectra(self): + dico_results = self.load() + + t = dico_results['times'] + Nt = t.size + nt = 2**int(np.fix(np.log2(Nt/10))) + # if nt%2 == 1: + # nt -= 1 + self.nt = nt + + if not hasattr(self, 'opfft1d'): + self.opfft1d = easypyfft.FFTW1D(nt) + + T = t[nt-1] - t[0] + # deltat = T/nt + self.deltaomega = 2*np.pi/T + # self.omega = self.deltaomega*np.concatenate( + # (np.arange(nt/2+1), np.arange(-nt/2+1, 0))) + + self.omega = self.deltaomega*np.arange(nt/2+1) + + self.hann = np.hanning(nt) + + nb_shells = dico_results['nb_shells'] + nb_k_per_shell = dico_results['nb_k_per_shell'] + # nb_k_tot = dico_results['nb_k_tot'] + + sig_q_fft = dico_results['sig_q_fft'] + sig_a_fft = dico_results['sig_a_fft'] + sig_d_fft = dico_results['sig_d_fft'] + + # kh_shell = dico_results['kh_shell'] + omega_shell = dico_results['omega_shell'] + # period_shell = 2*np.pi/omega_shell + + time_spectra_q = np.zeros([nb_shells, nt/2+1]) + time_spectra_a = np.zeros([nb_shells, nt/2+1]) + time_spectra_d = np.zeros([nb_shells, nt/2+1]) + + for ish in xrange(nb_shells): + coef_norm_a = self.c2/omega_shell[ish] + for ikps in xrange(nb_k_per_shell): + isig = ish*nb_k_per_shell+ikps + sig_a_fft[isig] *= coef_norm_a + time_spectra_q[ish] += self.time_spectrum(sig_q_fft[isig]) + time_spectra_a[ish] += self.time_spectrum(sig_a_fft[isig]) + time_spectra_d[ish] += self.time_spectrum(sig_d_fft[isig]) + + time_spectra_q /= nb_k_per_shell + time_spectra_a /= nb_k_per_shell + time_spectra_d /= nb_k_per_shell + + dico_spectra = { + 'omega': self.omega, + 'time_spectra_q': time_spectra_q, + 'time_spectra_a': time_spectra_a, + 'time_spectra_d': time_spectra_d} + return dico_spectra, dico_results + + def plot_spectra(self): + dico_spectra, dico_results = self.compute_spectra() + + omega = dico_spectra['omega'] + time_spectra_q = dico_spectra['time_spectra_q'] + time_spectra_a = dico_spectra['time_spectra_a'] + time_spectra_d = dico_spectra['time_spectra_d'] + omega_shell = dico_results['omega_shell'] + + fig, ax1 = self.output.figure_axe() + ax1.set_xlabel('r$\omega/\omega_{lin}$') + ax1.set_ylabel('r$E(\omega)$)') + title = ( + 'time spectra, solver ' + + self.output.name_solver + + ', nh = {0:5d}'.format(self.nx) + + ', c = {0:.4g}, f = {1:.4g}'.format(np.sqrt(self.c2), self.f) + ) + ax1.set_title(title) + ax1.hold(True) + + nb_shells = dico_results['nb_shells'] + for ish in xrange(nb_shells): + ax1.loglog(omega/omega_shell[ish], + time_spectra_q[ish], 'k', linewidth=1) + ax1.loglog(omega/omega_shell[ish], + time_spectra_a[ish], 'b', linewidth=1) + ax1.loglog(omega/omega_shell[ish], + time_spectra_d[ish], 'r', linewidth=1) + + def close_file(self): + try: + self.file.close() + except AttributeError: + pass + + +if __name__ == '__main__': + path_dir = '/scratch/augier/Results_for_article_SW1l/Waves_standing_256x256/SE2D_SW1lexlin_forcing_L=50.x50._256x256_c2=900_f=0_2013-06-04_12-49-26' + + from solveq2d import solveq2d + + sim = solveq2d.create_sim_plot_from_dir(path_dir) + + sim.output.time_sigK.plot_spectra() + solveq2d.show() diff --git a/fluidsim/base/params.py b/fluidsim/base/params.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9wYXJhbXMucHk= --- /dev/null +++ b/fluidsim/base/params.py @@ -0,0 +1,97 @@ +"""Information on a solver (:mod:`fluidsim.base.params`) +============================================================== + +.. currentmodule:: fluidsim.base.params + +Provides: + +.. autoclass:: Parameters + :members: + :private-members: + + +""" + +from __future__ import division, print_function + +import os + +from fluiddyn.util.containerxml import ContainerXML +from fluiddyn.util.util import import_class + + +class Parameters(ContainerXML): + """Contain the parameters.""" + pass + +from fluidsim.base.solvers.info_base import InfoSolverBase + + +def create_params(input_info_solver): + """Create a Parameters instance from an InfoSolverBase instance.""" + if isinstance(input_info_solver, InfoSolverBase): + info_solver = input_info_solver + elif hasattr(input_info_solver, 'info_solver'): + info_solver = input_info_solver.info_solver + else: + raise ValueError('input_info_solver is not related ' + 'to a InfoSolver instance.') + + params = Parameters(tag='params') + dict_classes = info_solver.import_classes() + + dict_classes['Solver'] = import_class( + info_solver.module_name, info_solver.class_name) + + for Class in dict_classes.values(): + if hasattr(Class, '_complete_params_with_default'): + try: + Class._complete_params_with_default(params) + except TypeError: + try: + Class._complete_params_with_default(params, info_solver) + except TypeError: + print('TypeError for ', Class) + raise + return params + + +def load_params_simul(path_dir=None): + """Load the parameters and return a Parameters instance.""" + if path_dir is None: + path_dir = os.getcwd() + return Parameters( + path_file=os.path.join(path_dir, 'params_simul.xml')) + + +def load_info_solver(path_dir=None): + """Load the solver information, return an InfoSolverBase instance. + + """ + if path_dir is None: + path_dir = os.getcwd() + return InfoSolverBase( + path_file=os.path.join(path_dir, 'info_solver.xml')) + + +# def load_info_simul(path_dir=None): +# """Load the data and gather them in a ContainerXML instance.""" + +# if path_dir is None: +# path_dir = os.getcwd() +# info_solver = load_info_solver(path_dir=path_dir) +# params = load_params_simul(path_dir=path_dir) +# info = ContainerXML(tag='info_simul') +# info.set_as_child(info_solver) +# info.set_as_child(params) +# return info + + +if __name__ == '__main__': + info_solver = InfoSolverBase(tag='solver') + + info_solver.complete_with_classes() + + params = create_params(info_solver) + + info = create_info_simul(info_solver, params) diff --git a/fluidsim/base/solvers/__init__.py b/fluidsim/base/solvers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9zb2x2ZXJzL19faW5pdF9fLnB5 --- /dev/null +++ b/fluidsim/base/solvers/__init__.py @@ -0,0 +1,15 @@ +"""Base simulations (:mod:`fluidsim.base.solvers`) +======================================================== + +.. currentmodule:: fluidsim.base.solvers + +Provides: + +.. autosummary:: + :toctree: + + base + pseudo_spect + finite_diff + +""" diff --git a/fluidsim/base/solvers/base.py b/fluidsim/base/solvers/base.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9zb2x2ZXJzL2Jhc2UucHk= --- /dev/null +++ b/fluidsim/base/solvers/base.py @@ -0,0 +1,174 @@ +"""Base solver (:mod:`fluidsim.base.solvers.base`) +======================================================== + +.. currentmodule:: fluidsim.base.solvers.base + +Provides: + +.. autoclass:: InfoSolverBase + :members: + :private-members: + +.. autoclass:: SimulBase + :members: + :private-members: + +""" + +import numpy as np + + +from fluidsim.operators.setofvariables import SetOfVariables + +from fluidsim.base.params import Parameters + +from fluidsim.base.solvers.info_base import ( + InfoSolverBase, create_info_simul) + + +class SimulBase(object): + """Represent a solver. + + This is the main base class which is inherited by the other + simulation classes. + + A :class:`SimulBase` object contains at least one object of the + classes: + + - :class:`fluidsim.base.params.Parameters` + - :class:`fluidsim.base.time_stepping.TimeSteppingBase` + - :class:`fluidsim.operators.operators.Operators` + - :class:`fluidsim.base.state.StateBase` + + Parameters + ---------- + + params : :class:`fluidsim.base.params.Parameters` + Parameters for the simulation. + + info_solver : :class:`fluidsim.base.solvers.info_base.InfoSolverBase` + Information about the particular solver. + + """ + + @staticmethod + def _complete_params_with_default(params): + """A static method used to complete the *params* container.""" + attribs = {'short_name_type_run': '', + 'NEW_DIR_RESULTS': True, + 'ONLY_COARSE_OPER': False, + 'FORCING': False, + # Physical parameters: + 'nu_2': 0.} + params.set_attribs(attribs) + + def __init__(self, params, info_solver=None): + # np.seterr(invalid='raise') + # np.seterr(over='raise') + np.seterr(all='warn') + np.seterr(under='ignore') + + if info_solver is None: + info_solver = InfoSolverBase() + info_solver.complete_with_classes() + elif not isinstance(info_solver, InfoSolverBase): + raise ValueError('info_solver must be an InfoSolverBase object.') + dico_classes = info_solver.import_classes() + + if not isinstance(params, Parameters): + raise TypeError('params should be a Parameters instance.') + + # params.check_and_modify() + self.params = params + self.info = create_info_simul(info_solver, params) + + # initialization operators and grid + Operators = dico_classes['Operators'] + self.oper = Operators(params=params) + + # initialization output + Output = dico_classes['Output'] + self.output = Output(self) + + self.output.print_stdout( + '*************************************\n' + + 'Program FluidDyn') + + # output.print_memory_usage( + # 'Memory usage after creating operator (equiv. seq.)') + + # initialisation object variables + State = dico_classes['State'] + self.state = State(self, info_solver) + + # initialisation time stepping + TimeStepping = dico_classes['TimeStepping'] + self.time_stepping = TimeStepping(self) + + # initialisation fields (and time if needed) + InitFields = dico_classes['InitFields'] + self.init_fields = InitFields(self) + self.init_fields() + + # just for the first output + if params.time_stepping.USE_CFL: + self.time_stepping._compute_time_increment_CLF() + + # initialisation forcing + if params.FORCING: + Forcing = dico_classes['Forcing'] + self.forcing = Forcing(params, self) + self.forcing.compute() + + # complete the initialisation of the object output + self.output.init_with_oper_and_state() + + def tendencies_nonlin(self, variables=None): + """Return a null SetOfVariables object.""" + tendencies = SetOfVariables( + like_this_sov=self.state.state_fft, + name_type_variables='tendencies_nonlin') + tendencies.initialize(value=0.) + return tendencies + + +Simul = SimulBase + + +if __name__ == "__main__": + + import fluiddyn as fld + + info_solver = InfoSolverBase() + info_solver.complete_with_classes() + + params = fld.simul.create_params(info_solver) + + params.short_name_type_run = 'test' + + nh = 16 + Lh = 2*np.pi + params.oper.nx = nh + params.oper.ny = nh + params.oper.Lx = Lh + params.oper.Ly = Lh + + delta_x = params.oper.Lx/params.oper.nx + params.nu_8 = 2.*10e-1*params.forcing.forcing_rate**(1./3)*delta_x**8 + + params.time_stepping.t_end = 5. + + params.init_fields.type_flow_init = 'NOISE' + + params.output.periods_plot.phys_fields = 0. + + params.output.periods_print.print_stdout = 0.25 + params.output.periods_save.phys_fields = 2. + + sim = Simul(params) + + sim.output.phys_fields.plot() + sim.time_stepping.start() + sim.output.phys_fields.plot() + + fld.show() diff --git a/fluidsim/base/solvers/finite_diff.py b/fluidsim/base/solvers/finite_diff.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9zb2x2ZXJzL2Zpbml0ZV9kaWZmLnB5 --- /dev/null +++ b/fluidsim/base/solvers/finite_diff.py @@ -0,0 +1,28 @@ + +from fluidsim.base.solvers.base import InfoSolverBase + + +class InfoSolverFiniteDiff(InfoSolverBase): + """Contain the information on a solver.""" + + def _init_root(self): + + super(InfoSolverFiniteDiff, self)._init_root() + + self.classes.set_child( + 'State', + attribs={'module_name': 'fluidsim.base.state', + 'class_name': 'StateBase'}) + + self.classes.set_child( + 'TimeStepping', + attribs={'module_name': + 'fluidsim.base.time_stepping.finite_diff', + 'class_name': + 'TimeSteppingFiniteDiffCrankNicolson'}) + + self.classes.set_child( + 'Operators', + attribs={'module_name': + 'fluidsim.operators.op_finitediff', + 'class_name': 'OperatorFiniteDiff1DPeriodic'}) diff --git a/fluidsim/base/solvers/info_base.py b/fluidsim/base/solvers/info_base.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9zb2x2ZXJzL2luZm9fYmFzZS5weQ== --- /dev/null +++ b/fluidsim/base/solvers/info_base.py @@ -0,0 +1,82 @@ + +from copy import deepcopy + +from fluiddyn.util.containerxml import ContainerXML +from fluiddyn.util.util import import_class + + +def create_info_simul(info_solver, params): + """Create a ContainerXML instance gathering info_solver and params.""" + info = ContainerXML(tag='info_simul') + info.set_as_child(info_solver) + info.set_as_child(params) + return info + + +def _merged_element(el1, el2): + result = deepcopy(el1) + result.extend(deepcopy(el2)) + return result + + +class InfoSolverBase(ContainerXML): + """Contain the information on a solver.""" + def __init__(self, **kargs): + + if 'tag' not in kargs: + kargs['tag'] = 'solver' + + super(InfoSolverBase, self).__init__(**kargs) + + if kargs['tag'] == 'solver' and 'path_file' not in kargs: + self._init_root() + + def _init_root(self): + + self.set_attribs({'module_name': 'fluidsim.base.solvers.base', + 'class_name': 'SimulBase', + 'short_name': 'Base'}) + + self.set_child('classes') + + self.classes.set_child( + 'InitFields', + attribs={'module_name': 'fluidsim.base.init_fields', + 'class_name': 'InitFieldsBase'}) + + self.classes.set_child( + 'Forcing', + attribs={'module_name': 'fluidsim.base.forcing', + 'class_name': 'ForcingBase'}) + + self.classes.set_child( + 'Output', + attribs={'module_name': 'fluidsim.base.output.base', + 'class_name': 'OutputBase'}) + + def import_classes(self): + """Import the classes and return a dictionary.""" + classes = self._elemxml.findall('classes') + dict_classes = {} + if len(classes) == 0: + return dict_classes + classes = reduce(_merged_element, classes) + for c in classes.getchildren(): + try: + module_name = c.attrib['module_name'] + class_name = c.attrib['class_name'] + except KeyError: + pass + else: + Class = import_class(module_name, class_name) + dict_classes[c.tag] = Class + + return dict_classes + + def complete_with_classes(self): + dict_classes = self.import_classes() + for Class in dict_classes.values(): + if hasattr(Class, '_complete_info_solver'): + Class._complete_info_solver(self) + + diff --git a/fluidsim/base/solvers/pseudo_spect.py b/fluidsim/base/solvers/pseudo_spect.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9zb2x2ZXJzL3BzZXVkb19zcGVjdC5weQ== --- /dev/null +++ b/fluidsim/base/solvers/pseudo_spect.py @@ -0,0 +1,140 @@ +"""Base solver (:mod:`fluidsim.base.solvers.pseudo_spect`) +================================================================ + +.. currentmodule:: fluidsim.base.solvers.pseudo_spect + +Provides: + +.. autoclass:: InfoSolverPseudoSpectral + :members: + :private-members: + +.. autoclass:: SimulBasePseudoSpectral + :members: + :private-members: + +""" + +import numpy as np + +from fluiddyn.util import mpi + +from fluidsim.operators.setofvariables import SetOfVariables + +from fluidsim.base.solvers.base import SimulBase, InfoSolverBase + + +class InfoSolverPseudoSpectral(InfoSolverBase): + """Contain the information on a solver.""" + + def _init_root(self): + + super(InfoSolverPseudoSpectral, self)._init_root() + + self.module_name = 'fluidsim.base.solvers.pseudo_spect' + self.class_name = 'SimulBasePseudoSpectral' + self.short_name = 'BasePS' + + self.classes.set_child( + 'State', + attribs={'module_name': 'fluidsim.base.state', + 'class_name': 'StatePseudoSpectral'}) + + self.classes.set_child( + 'TimeStepping', + attribs={'module_name': + 'fluidsim.base.time_stepping.pseudo_spect_cy', + 'class_name': 'TimeSteppingPseudoSpectral'}) + + self.classes.set_child( + 'Operators', + attribs={'module_name': 'fluidsim.operators.operators', + 'class_name': 'OperatorsPseudoSpectral2D'}) + + +info_solver_ps = InfoSolverPseudoSpectral() +info_solver_ps.complete_with_classes() + + +class SimulBasePseudoSpectral(SimulBase): + + @staticmethod + def _complete_params_with_default(params): + """A static method used to complete the *params* container.""" + SimulBase._complete_params_with_default(params) + + attribs = {'nu_8': 0., + 'nu_4': 0., + 'nu_m4': 0.} + params.set_attribs(attribs) + + def __init__(self, params, info_solver=info_solver_ps): + super(SimulBasePseudoSpectral, self).__init__(params, info_solver) + + def compute_freq_diss(self): + if self.params.nu_2 > 0: + f_d = self.params.nu_2*self.oper.K2 + else: + f_d = np.zeros_like(self.oper.K2) + + if self.params.nu_4 > 0.: + f_d += self.params.nu_4*self.oper.K4 + + if self.params.nu_8 > 0.: + f_d += self.params.nu_8*self.oper.K8 + + if self.params.nu_m4 > 0.: + f_d_hypo = self.params.nu_m4/self.oper.K2_not0**2 + # mode K2 = 0 ! + if mpi.rank == 0: + f_d_hypo[0, 0] = f_d_hypo[0, 1] + else: + f_d_hypo = np.zeros_like(f_d) + + return f_d, f_d_hypo + + def tendencies_nonlin(self, variables=None): + """Return a null SetOfVariables object.""" + tendencies = SetOfVariables( + like_this_sov=self.state.state_fft, + name_type_variables='tendencies_nonlin') + tendencies.initialize(value=0.) + return tendencies + +Simul = SimulBasePseudoSpectral + + +if __name__ == "__main__": + + import fluiddyn as fld + + params = fld.simul.create_params(info_solver_ps) + + params.short_name_type_run = 'test' + + nh = 16 + Lh = 2*np.pi + params.oper.nx = nh + params.oper.ny = nh + params.oper.Lx = Lh + params.oper.Ly = Lh + + delta_x = params.oper.Lx/params.oper.nx + params.nu_8 = 2.*10e-1*params.forcing.forcing_rate**(1./3)*delta_x**8 + + params.time_stepping.t_end = 5. + + params.init_fields.type_flow_init = 'NOISE' + + params.output.periods_plot.phys_fields = 0. + + params.output.periods_print.print_stdout = 0.25 + params.output.periods_save.phys_fields = 2. + + sim = Simul(params) + + sim.output.phys_fields.plot() + sim.time_stepping.start() + sim.output.phys_fields.plot() + + fld.show() diff --git a/fluidsim/base/state.py b/fluidsim/base/state.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS9zdGF0ZS5weQ== --- /dev/null +++ b/fluidsim/base/state.py @@ -0,0 +1,161 @@ +"""State of the variables (:mod:`fluidsim.base.state`) +============================================================ + +.. currentmodule:: fluidsim.base.state + +Provides: + +.. autoclass:: StateBase + :members: + :private-members: + +.. autoclass:: StatePseudoSpectral + :members: + :private-members: + +""" + +import numpy as np + +from fluidsim.operators.setofvariables import SetOfVariables + + +class StateBase(object): + """Contains the state variables and handles the access to fields.""" + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + info_solver.classes.State.set_attribs( + {'keys_state_phys': ['ux', 'uy'], + 'keys_computable': [], + 'keys_phys_needed': ['ux', 'uy']}) + + def __init__(self, sim, info_solver): + self.sim = sim + self.params = sim.params + self.oper = sim.oper + + # creation of the SetOfVariables state_fft and state_phys + self.keys_state_phys = info_solver.classes.State.keys_state_phys + self.keys_computable = info_solver.classes.State.keys_computable + + self.state_phys = SetOfVariables(keys=self.keys_state_phys, + shape1var=self.oper.shapeX_loc, + dtype=np.float64, + name_type_variables='state_phys' + ) + self.vars_computed = {} + self.it_computed = {} + + def compute(self, key): + pass + + def clear_computed(self): + self.vars_computed.clear() + + def __call__(self, key): + if key in self.keys_state_phys: + return self.state_phys[key] + else: + it = self.sim.time_stepping.it + if (key in self.vars_computed and it == self.it_computed[key]): + return self.vars_computed[key] + else: + value = self.compute(key) + self.vars_computed[key] = value + self.it_computed[key] = it + return value + + def __setitem__(self, key, value): + if key in self.keys_state_phys: + self.state_phys[key] = value + else: + raise ValueError('key "'+key+'" is not known') + + def can_this_key_be_obtained(self, key): + return (key in self.keys_state_phys or + key in self.keys_computable) + + +class StatePseudoSpectral(StateBase): + """Contains the state variables and handles the access to fields. + + This is the general class for the pseudo-spectral solvers. + + """ + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + + StateBase._complete_info_solver(info_solver) + + info_solver.classes.State.set_attribs( + {'keys_state_fft': ['ux_fft', 'uy_fft']}) + + def __init__(self, sim, info_solver): + + super(StatePseudoSpectral, self).__init__(sim, info_solver) + + self.keys_state_fft = info_solver.classes.State['keys_state_fft'] + self.state_fft = SetOfVariables(keys=self.keys_state_fft, + shape1var=self.oper.shapeK_loc, + dtype=np.complex128, + name_type_variables='state_fft') + + def __call__(self, key): + """Return the variable corresponding to the given key.""" + if key in self.keys_state_fft: + return self.state_fft[key] + elif key in self.keys_state_phys: + return self.state_phys[key] + else: + it = self.sim.time_stepping.it + if (key in self.vars_computed and it == self.it_computed[key]): + return self.vars_computed[key] + else: + value = self.compute(key) + self.vars_computed[key] = value + self.it_computed[key] = it + return value + + def __setitem__(self, key, value): + if key in self.keys_state_fft: + self.state_fft[key] = value + elif key in self.keys_state_phys: + self.state_phys[key] = value + else: + raise ValueError('key "'+key+'" is not known') + + def statefft_from_statephys(self): + fft2 = self.oper.fft2 + for ik in xrange(self.state_fft.nb_variables): + self.state_fft.data[ik][:] = fft2(self.state_phys.data[ik]) + + def statephys_from_statefft(self): + ifft2 = self.oper.ifft2 + for ik in xrange(self.state_fft.nb_variables): + self.state_phys.data[ik] = ifft2(self.state_fft.data[ik]) + + def return_statephys_from_statefft(self, state_fft=None): + """Return the state in physical space.""" + ifft2 = self.oper.ifft2 + if state_fft is None: + state_fft = self.state_fft + + state_phys = SetOfVariables(like_this_sov=self.state_phys) + for ik in xrange(self.state_fft.nb_variables): + state_phys.data[ik] = ifft2(state_fft.data[ik]) + return state_phys + + def can_this_key_be_obtained(self, key): + return (key in self.keys_state_phys or + key in self.keys_computable or + key in self.keys_state_fft) diff --git a/fluidsim/base/test/__init__.py b/fluidsim/base/test/__init__.py new file mode 100644 diff --git a/fluidsim/base/test/test_base_solver.py b/fluidsim/base/test/test_base_solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS90ZXN0L3Rlc3RfYmFzZV9zb2x2ZXIucHk= --- /dev/null +++ b/fluidsim/base/test/test_base_solver.py @@ -0,0 +1,42 @@ + +import unittest + +import numpy as np + +import fluiddyn as fld + +from fluidsim.base.solvers.pseudo_spect import ( + SimulBasePseudoSpectral, info_solver_ps) + +from fluiddyn.io import stdout_redirected + + +class TestBaseSolver(unittest.TestCase): + def test_simul(self): + """Should be able to run a base experiment.""" + + params = fld.simul.create_params(info_solver_ps) + + params.short_name_type_run = 'test' + + nh = 16 + Lh = 2*np.pi + params.oper.nx = nh + params.oper.ny = nh + params.oper.Lx = Lh + params.oper.Ly = Lh + + params.nu_2 = 1. + + params.time_stepping.t_end = 2. + + params.output.periods_plot.phys_fields = 0. + + with stdout_redirected(): + sim = SimulBasePseudoSpectral(params) + sim.time_stepping.start() + + fld.show() + +if __name__ == '__main__': + unittest.main() diff --git a/fluidsim/base/time_stepping/__init__.py b/fluidsim/base/time_stepping/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS90aW1lX3N0ZXBwaW5nL19faW5pdF9fLnB5 --- /dev/null +++ b/fluidsim/base/time_stepping/__init__.py @@ -0,0 +1,16 @@ +"""Time stepping (:mod:`fluidsim.base.time_stepping`) +=========================================================== + +.. currentmodule:: fluidsim.base.time_stepping + +Provides: + +.. autosummary:: + :toctree: + + base + pseudo_spect + pseudo_spect_cy + finite_diff + +""" diff --git a/fluidsim/base/time_stepping/base.py b/fluidsim/base/time_stepping/base.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS90aW1lX3N0ZXBwaW5nL2Jhc2UucHk= --- /dev/null +++ b/fluidsim/base/time_stepping/base.py @@ -0,0 +1,207 @@ +"""Time stepping (:mod:`fluidsim.base.time_stepping.base`) +================================================================ + +.. currentmodule:: fluidsim.base.time_stepping.base + +Provides: + +.. autoclass:: TimeSteppingBase + :members: + :private-members: + +""" + +from time import time + +from fluiddyn.util import mpi + + +class TimeSteppingBase(object): + """Universal time stepping class used for all solvers. + + + """ + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + attribs = {'USE_T_END': True, + 't_end': 10., + 'it_end': 10, + 'USE_CFL': True, + 'type_time_scheme': 'RK4', + 'deltat0': 0.2} + params.set_child('time_stepping', attribs=attribs) + + def _init_compute_time_step(self): + + params_ts = self.params.time_stepping + + if params_ts.USE_CFL: + if params_ts.type_time_scheme == 'RK2': + self.CFL = 0.4 + elif params_ts.type_time_scheme == 'RK4': + self.CFL = 1.0 + else: + raise ValueError('Problem name time_scheme') + else: + self.deltat = params_ts.deltat0 + + self.deltat = params_ts.deltat0 + + has_ux = self.sim.state.can_this_key_be_obtained('ux') + has_uy = self.sim.state.can_this_key_be_obtained('uy') + has_uz = self.sim.state.can_this_key_be_obtained('uz') + + if has_ux and has_uy and has_uz: + self._compute_time_increment_CLF = \ + self._compute_time_increment_CLF_uxuyuz + elif has_ux and has_uy: + self._compute_time_increment_CLF = \ + self._compute_time_increment_CLF_uxuy + elif has_ux: + self._compute_time_increment_CLF = \ + self._compute_time_increment_CLF_ux + else: + self._compute_time_increment_CLF = \ + self._compute_time_increment_CLF_no_ux + + self.deltat_max = 0.2 + + def _init_time_scheme(self): + + params_ts = self.params.time_stepping + + if params_ts.type_time_scheme == 'RK2': + self._time_step_RK = self._time_step_RK2 + elif params_ts.type_time_scheme == 'RK4': + self._time_step_RK = self._time_step_RK4 + else: + raise ValueError('Problem name time_scheme') + + def start(self): + """Loop to run the function :func:`one_time_step`. + + If *self.USE_T_END* is true, run till ``t >= t_end``, + otherwise run *self.it_end* time steps. + """ + print_stdout = self.sim.output.print_stdout + print_stdout( + '*************************************\n' + + 'Beginning of the computation') + if self.sim.output.has_to_save: + self.sim.output.phys_fields.save() + time_begining_simul = time() + if self.params.time_stepping.USE_T_END: + print_stdout( + ' compute until t = {0:10.6g}'.format( + self.params.time_stepping.t_end)) + while self.t < self.params.time_stepping.t_end: + self.one_time_step() + else: + print_stdout( + ' compute until it = {0:8d}'.format( + self.params.time_stepping.it_end)) + while self.it < self.params.time_stepping.it_end: + self.one_time_step() + total_time_simul = time() - time_begining_simul + self.sim.output.end_of_simul(total_time_simul) + + def one_time_step(self): + if self.params.time_stepping.USE_CFL: + self._compute_time_increment_CLF() + if self.params.FORCING: + self.sim.forcing.compute() + self.sim.output.one_time_step() + self.one_time_step_computation() + + def _compute_time_increment_CLF_uxuyuz(self): + """Compute the time increment deltat with a CLF condition.""" + + ux = self.sim.state('ux') + uy = self.sim.state('uy') + uz = self.sim.state('uz') + + max_ux = abs(ux).max() + max_uy = abs(uy).max() + max_uz = abs(uz).max() + temp = (max_ux/self.sim.oper.deltax + + max_uy/self.sim.oper.deltay + + max_uz/self.sim.oper.deltaz) + + if mpi.nb_proc > 1: + temp = mpi.comm.allreduce(temp, op=mpi.MPI.MAX) + + if temp > 0: + deltat_CFL = self.CFL/temp + else: + deltat_CFL = self.deltat_max + + maybe_new_dt = min(deltat_CFL, self.deltat_max) + normalize_diff = abs(self.deltat-maybe_new_dt)/maybe_new_dt + + if normalize_diff > 0.02: + self.deltat = maybe_new_dt + + def _compute_time_increment_CLF_uxuy(self): + """Compute the time increment deltat with a CLF condition.""" + + ux = self.sim.state('ux') + uy = self.sim.state('uy') + + max_ux = abs(ux).max() + max_uy = abs(uy).max() + temp = (max_ux/self.sim.oper.deltax + max_uy/self.sim.oper.deltay) + + if mpi.nb_proc > 1: + temp = mpi.comm.allreduce(temp, op=mpi.MPI.MAX) + + if temp > 0: + deltat_CFL = self.CFL/temp + else: + deltat_CFL = self.deltat_max + + maybe_new_dt = min(deltat_CFL, self.deltat_max) + normalize_diff = abs(self.deltat-maybe_new_dt)/maybe_new_dt + + if normalize_diff > 0.02: + self.deltat = maybe_new_dt + + def _compute_time_increment_CLF_ux(self): + """Compute the time increment deltat with a CLF condition.""" + ux = self.sim.state('ux') + max_ux = abs(ux).max() + temp = max_ux/self.sim.oper.deltax + + if mpi.nb_proc > 1: + temp = mpi.comm.allreduce(temp, op=mpi.MPI.MAX) + + if temp > 0: + deltat_CFL = self.CFL/temp + else: + deltat_CFL = self.deltat_max + + maybe_new_dt = min(deltat_CFL, self.deltat_max) + normalize_diff = abs(self.deltat-maybe_new_dt)/maybe_new_dt + + if normalize_diff > 0.02: + self.deltat = maybe_new_dt + + def _compute_time_increment_CLF_no_ux(self): + """Compute the time increment deltat with a CLF condition.""" + max_ux = self.params.U + temp = max_ux/self.sim.oper.deltax + + if mpi.nb_proc > 1: + temp = mpi.comm.allreduce(temp, op=mpi.MPI.MAX) + + if temp > 0: + deltat_CFL = self.CFL/temp + else: + deltat_CFL = self.deltat_max + + maybe_new_dt = min(deltat_CFL, self.deltat_max) + normalize_diff = abs(self.deltat-maybe_new_dt)/maybe_new_dt + + if normalize_diff > 0.02: + self.deltat = maybe_new_dt diff --git a/fluidsim/base/time_stepping/finite_diff.py b/fluidsim/base/time_stepping/finite_diff.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS90aW1lX3N0ZXBwaW5nL2Zpbml0ZV9kaWZmLnB5 --- /dev/null +++ b/fluidsim/base/time_stepping/finite_diff.py @@ -0,0 +1,144 @@ +"""Time stepping (:mod:`fluidsim.base.time_stepping.finite_diff`) +======================================================================= + +.. currentmodule:: fluidsim.base.time_stepping.finite_diff + +Provides: + +.. autoclass:: TimeSteppingFiniteDiffCrankNicolson + :members: + :private-members: + +""" + +import numpy as np +import scipy.sparse as sparse +from scipy.sparse.linalg import spsolve + +from copy import deepcopy + +from fluidsim.operators.setofvariables import SetOfVariables + +from .base import TimeSteppingBase + + +class TimeSteppingFiniteDiffCrankNicolson(TimeSteppingBase): + """ + Time stepping class for finite-difference solvers. + + """ + def __init__(self, sim): + self.params = sim.params + self.sim = sim + + self.it = 0 + self.t = 0 + + # self._init_freq_lin() + self._init_compute_time_step() + self._init_time_scheme() + + self.L = sim.linear_operator() + self.set_of_vars_temp = SetOfVariables( + like_this_sov=sim.state.state_phys) + + def one_time_step_computation(self): + """One time step""" + self._time_step_RK() + self.t += self.deltat + self.it += 1 + if np.isnan(np.min(self.sim.state.state_phys.data)): + raise ValueError( + 'nan at it = {0}, t = {1:.4f}'.format(self.it, self.t)) + + def _time_step_RK2(self): + r"""Advance in time the variables with the Runge-Kutta 2 method. + + .. _rk2timeschemeFiniteDiff: + + Notes + ----- + + .. Look at Simson KTH documentation... + (http://www.mech.kth.se/~mattias/simson-user-guide-v4.0.pdf) + + The Runge-Kutta 2 method computes an approximation of the + solution after a time increment :math:`dt`. We denote the + initial time :math:`t = 0`. + + For the finite difference schemes, We consider an equation of the form + + .. math:: \p_t S = L S + N(S), + + The linear term can be treated with an implicit method while + the nonlinear term have to be treated with an explicit method + (see for example `Explicit and implicit methods + <http://en.wikipedia.org/wiki/Explicit_and_implicit_methods>`_). + + - Approximation 1: + + For the first step where the nonlinear term is approximated + as :math:`N(S) \simeq N(S_0)`, we obtain + + .. math:: + \left( 1 - \frac{dt}{4} L \right) S_{A1dt/2} + \simeq \left( 1 + \frac{dt}{4} L \right) S_0 + N(S_0)dt/2 + + Once the right-hand side has been computed, a linear + equation has to be solved. It is not efficient to invert the + matrix :math:`1 + \frac{dt}{2} L` so other methods have to + be used, as the `Thomas algorithm + <http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm>`_, + or algorithms based on the LU or the QR decompositions. + + - Approximation 2: + + The nonlinear term is then approximated as :math:`N(S) + \simeq N(S_{A1dt/2})`, which gives + + .. math:: + \left( 1 - \frac{dt}{2} L \right) S_{A2dt} + \simeq \left( 1 + \frac{dt}{2} L \right) S_0 + N(S_{A1dt/2})dt + + """ + dt = self.deltat + sim = self.sim + identity = sparse.identity(sim.state.state_phys.data.size) + + # it seems that there is a bug with the proper RK2 method + # (it "goes too fast") + + # # approximation 1 (at t + dt/2 -> "A1dt2"): + # tendenciesNL_0 = sim.tendencies_nonlin() + # rhs_A1dt2 = self.right_hand_side(sim.state.state_phys, + # tendenciesNL_0, dt/2) + + # A_A1dt2 = identity - dt/4*self.L + # S_A1dt2 = self.invert_to_get_solution(A_A1dt2, rhs_A1dt2) + # del(rhs_A1dt2, A_A1dt2) + + # # approximation 2 (at t + dt -> "A2dt"): + # tendenciesNL_1 = sim.tendencies_nonlin(S_A1dt2) + # rhs_A2dt = self.right_hand_side(S_A1dt2, tendenciesNL_1, dt) + # A_A2dt = identity - dt/2*self.L + # sim.state.state_phys = deepcopy( + # self.invert_to_get_solution(A_A2dt, rhs_A2dt)) + + # it seems to work with the basic Newton time stepping: + tendenciesNL_0 = sim.tendencies_nonlin() + rhs_A1dt = self.right_hand_side(sim.state.state_phys, + tendenciesNL_0, dt) + A_A1dt = identity - dt/2*self.L + sim.state.state_phys = deepcopy( + self.invert_to_get_solution(A_A1dt, rhs_A1dt)) + + def right_hand_side(self, S, N, dt): + return (S.data.ravel() + + dt/2*self.L.dot(S.data.flat) + + dt*N.data.ravel()) + + def invert_to_get_solution(self, A, b): + """Solve the linear system :math:`Ax = b`.""" + self.set_of_vars_temp.data = spsolve(A, b).reshape( + self.set_of_vars_temp.data.shape) + return self.set_of_vars_temp diff --git a/fluidsim/base/time_stepping/pseudo_spect.py b/fluidsim/base/time_stepping/pseudo_spect.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS90aW1lX3N0ZXBwaW5nL3BzZXVkb19zcGVjdC5weQ== --- /dev/null +++ b/fluidsim/base/time_stepping/pseudo_spect.py @@ -0,0 +1,289 @@ +"""Time stepping (:mod:`fluidsim.base.time_stepping.pseudo_spect`) +======================================================================== + +.. currentmodule:: fluidsim.base.time_stepping.pseudo_spect + +Provides: + +.. autoclass:: TimeSteppingPseudoSpectral + :members: + :private-members: + +""" + +import numpy as np + +from .base import TimeSteppingBase + + +class ExactLinearCoefs(object): + """Handle the computation of the exact coefficient for the RK4.""" + + def __init__(self, time_stepping): + self.time_stepping = time_stepping + sim = time_stepping.sim + self.shapeK_loc = sim.oper.shapeK_loc + self.freq_lin = time_stepping.freq_lin + + self.exact = np.empty_like(self.freq_lin) + self.exact2 = np.empty_like(self.freq_lin) + + if sim.params.time_stepping.USE_CFL: + self.get_updated_coefs = self.get_updated_coefs_CLF + self.dt_old = 0. + else: + self.compute(time_stepping.deltat) + self.get_updated_coefs = self.get_coefs + + def compute(self, dt): + f_lin = self.freq_lin + self.exact = np.exp(-dt*f_lin) + self.exact2 = np.exp(-dt/2*f_lin) + self.dt_old = dt + + def get_updated_coefs_CLF(self): + dt = self.time_stepping.deltat + if self.dt_old != dt: + self.compute(dt) + return self.exact, self.exact2 + + def get_coefs(self): + return self.exact, self.exact2 + + +class TimeSteppingPseudoSpectral(TimeSteppingBase): + """Time stepping class for pseudo-spectral solvers. + + """ + def __init__(self, sim): + self.params = sim.params + self.sim = sim + + self.it = 0 + self.t = 0 + + self._init_freq_lin() + self._init_compute_time_step() + self._init_exact_linear_coef() + self._init_time_scheme() + + def _init_freq_lin(self): + f_d, f_d_hypo = self.sim.compute_freq_diss() + freq_dissip = f_d + f_d_hypo + + if hasattr(self.sim, 'compute_freq_complex'): + freq_complex = self._compute_freq_complex() + self.freq_lin = freq_dissip + freq_complex + freq_max = freq_complex.imag.max() + self.deltat_max = 0.78*np.pi/freq_max + else: + self.freq_lin = freq_dissip + + def _compute_freq_complex(self): + state_fft = self.sim.state.state_fft + freq_complex = np.empty_like(state_fft.data) + for ik, key in enumerate(state_fft.keys): + freq_complex[ik] = self.sim.compute_freq_complex(key) + return freq_complex + + def _init_exact_linear_coef(self): + self.exact_linear_coefs = ExactLinearCoefs(self) + + def one_time_step_computation(self): + """One time step""" + self._time_step_RK() + self.sim.oper.dealiasing(self.sim.state.state_fft) + self.sim.state.statephys_from_statefft() + self.t += self.deltat + self.it += 1 + if np.isnan(np.min(self.sim.state.state_fft.data[0])): + raise ValueError( + 'nan at it = {0}, t = {1:.4f}'.format(self.it, self.t)) + + def _time_step_RK2(self): + r"""Advance in time with the Runge-Kutta 2 method. + + .. _rk2timescheme: + + Notes + ----- + + .. |p| mathmacro:: \partial + + We consider an equation of the form + + .. math:: \p_t S = \sigma S + N(S), + + The Runge-Kutta 2 method computes an approximation of the + solution after a time increment :math:`dt`. We denote the + initial time :math:`t = 0`. + + - Approximation 1: + + .. math:: \p_t \log S = \sigma + \frac{N(S_0)}{S_0}, + + Integrating from :math:`t` to :math:`t+dt/2`, it gives: + + .. |SA1halfdt| mathmacro:: S_{A1dt/2} + + .. math:: \SA1halfdt = (S_0 + N_0 dt/2) e^{\frac{\sigma dt}{2}}. + + + - Approximation 2: + + .. math:: + \p_t \log S = \sigma + + \frac{N(\SA1halfdt)}{ \SA1halfdt }, + + Integrating from :math:`t` to :math:`t+dt` and retaining + only the terms in :math:`dt^1` gives: + + .. math:: + S_{dtA2} = S_0 e^{\sigma dt} + + N(\SA1halfdt) dt e^{\frac{\sigma dt}{2}}. + + """ + dt = self.deltat + diss, diss2 = self.exact_linear_coefs.get_updated_coefs() + + tendencies_nonlin = self.sim.tendencies_nonlin + state_fft = self.sim.state.state_fft + + tendencies_fft_n = tendencies_nonlin() + state_fft_n12 = (state_fft + dt/2*tendencies_fft_n)*diss2 + tendencies_fft_n12 = tendencies_nonlin(state_fft_n12) + self.sim.state.state_fft = (state_fft*diss + + dt*diss2*tendencies_fft_n12) + + def _time_step_RK4(self): + r"""Advance in time with the Runge-Kutta 4 method. + + .. _rk4timescheme: + + We consider an equation of the form + + .. math:: \p_t S = \sigma S + N(S), + + The Runge-Kutta 4 method computes an approximation of the + solution after a time increment :math:`dt`. We denote the + initial time as :math:`t = 0`. This time scheme uses 4 + approximations. Only the terms in :math:`dt^1` are retained. + + - Approximation 1: + + .. math:: \p_t \log S = \sigma + \frac{N(S_0)}{S_0}, + + Integrating from :math:`t` to :math:`t+dt/2` gives: + + .. math:: \SA1halfdt = (S_0 + N_0 dt/2) e^{\sigma \frac{dt}{2}}. + + Integrating from :math:`t` to :math:`t+dt` gives: + + .. math:: S_{A1dt} = (S_0 + N_0 dt) e^{\sigma dt}. + + + - Approximation 2: + + .. math:: + \p_t \log S = \sigma + + \frac{N(\SA1halfdt)}{ \SA1halfdt }, + + Integrating from :math:`t` to :math:`t+dt/2` gives: + + .. |SA2halfdt| mathmacro:: S_{A2 dt/2} + + .. math:: + \SA2halfdt = S_0 e^{\sigma \frac{dt}{2}} + + N(\SA1halfdt) \frac{dt}{2}. + + Integrating from :math:`t` to :math:`t+dt` gives: + + .. math:: + S_{A2dt} = S_0 e^{\sigma dt} + + N(\SA1halfdt) e^{\sigma \frac{dt}{2}} dt. + + + - Approximation 3: + + .. math:: + \p_t \log S = \sigma + + \frac{N(\SA2halfdt)}{ \SA2halfdt }, + + Integrating from :math:`t` to :math:`t+dt` gives: + + .. math:: + S_{A3dt} = S_0 e^{\sigma dt} + + N(\SA2halfdt) e^{\sigma \frac{dt}{2}} dt. + + - Approximation 4: + + .. math:: + \p_t \log S = \sigma + + \frac{N(S_{A3dt})}{ S_{A3dt} }, + + Integrating from :math:`t` to :math:`t+dt` gives: + + .. math:: + S_{A4dt} = S_0 e^{\sigma dt} + N(S_{A3dt}) dt. + + + The final result is a pondered average of the results of 4 + approximations for the time :math:`t+dt`: + + .. math:: + \frac{1}{3} \left[ + \frac{1}{2} S_{A1dt} + + S_{A2dt} + S_{A3dt} + + \frac{1}{2} S_{A4dt} + \right], + + which is equal to: + + .. math:: + S_0 e^{\sigma dt} + + \frac{dt}{3} \left[ + \frac{1}{2} N(S_0) e^{\sigma dt} + + N(\SA1halfdt) e^{\sigma \frac{dt}{2}} + + N(\SA2halfdt) e^{\sigma \frac{dt}{2}} + + \frac{1}{2} N(S_{A3dt})\right]. + + """ + + dt = self.deltat + diss, diss2 = self.exact_linear_coefs.get_updated_coefs() + + tendencies_nonlin = self.sim.tendencies_nonlin + state_fft = self.sim.state.state_fft + + tendencies_fft_0 = tendencies_nonlin() + + # based on approximation 1 + state_fft_temp = (state_fft + + dt/6*tendencies_fft_0)*diss + state_fft_np12_approx1 = (state_fft + + dt/2*tendencies_fft_0)*diss2 + + del(tendencies_fft_0) + tendencies_fft_1 = tendencies_nonlin(state_fft_np12_approx1) + del(state_fft_np12_approx1) + + # based on approximation 2 + state_fft_temp += dt/3*diss2*tendencies_fft_1 + state_fft_np12_approx2 = (state_fft*diss2 + + dt/2*tendencies_fft_1) + + del(tendencies_fft_1) + tendencies_fft_2 = tendencies_nonlin(state_fft_np12_approx2) + del(state_fft_np12_approx2) + + # based on approximation 3 + state_fft_temp += dt/3*diss2*tendencies_fft_2 + state_fft_np1_approx = (state_fft*diss + + dt*diss2*tendencies_fft_2) + + del(tendencies_fft_2) + tendencies_fft_3 = tendencies_nonlin(state_fft_np1_approx) + del(state_fft_np1_approx) + + # result using the 4 approximations + self.sim.state.state_fft = state_fft_temp + dt/6*tendencies_fft_3 diff --git a/fluidsim/base/time_stepping/pseudo_spect_cy.pyx b/fluidsim/base/time_stepping/pseudo_spect_cy.pyx new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vYmFzZS90aW1lX3N0ZXBwaW5nL3BzZXVkb19zcGVjdF9jeS5weXg= --- /dev/null +++ b/fluidsim/base/time_stepping/pseudo_spect_cy.pyx @@ -0,0 +1,521 @@ +""" +Time stepping Cython (:mod:`fluidsim.base.time_stepping.pseudo_spect_cy`) +========================================================================= + +.. currentmodule:: fluidsim.base.time_stepping.pseudo_spect_cy + +Provides: + +.. autoclass:: ExactLinearCoefs + :members: + :private-members: + +.. autoclass:: TimeSteppingPseudoSpectral + :members: + :private-members: + +""" + +cimport numpy as np +import numpy as np +np.import_array() + +from time import time, sleep +import datetime +import os +import matplotlib.pyplot as plt +import cython + +from libc.math cimport exp + +from fluidsim.operators.setofvariables import SetOfVariables + +from pseudo_spect import ExactLinearCoefs as ExactLinearCoefsPurePython +from pseudo_spect import TimeSteppingPseudoSpectral as \ + TimeSteppingPseudoSpectralPurePython + + +# we define python and c types for physical and Fourier spaces +DTYPEb = np.uint8 +ctypedef np.uint8_t DTYPEb_t +DTYPEi = np.int +ctypedef np.int_t DTYPEi_t +DTYPEf = np.float64 +ctypedef np.float64_t DTYPEf_t +DTYPEc = np.complex128 +ctypedef np.complex128_t DTYPEc_t + +# Basically, you use the _t ones when you need to declare a type +# (e.g. cdef foo_t var, or np.ndarray[foo_t, ndim=...]. Ideally someday +# we won't have to make this distinction, but currently one is a C type +# and the other is a python object representing a numpy type (a dtype), +# and there's currently no way to identify the two without special +# compiler support. +# - Robert Bradshaw + + +cdef extern from "complex.h": + np.complex128_t cexp(np.complex128_t z) nogil + + +class ExactLinearCoefs(ExactLinearCoefsPurePython): + """Handle the computation of the exact coefficient for the RK4.""" + + def __init__(self, time_stepping): + super(ExactLinearCoefs, self).__init__(time_stepping) + + ndim = self.freq_lin.ndim + dtype = self.freq_lin.dtype + + if ndim == 2 and dtype == np.float64: + self.compute = self.compute_ndim2_float64 + else: + raise NotImplementedError( + 'ndim: {} ; dtype {}'.format(ndim, dtype)) + + @cython.boundscheck(False) + @cython.wraparound(False) + def compute_ndim2_float64(self, double dt): + cdef Py_ssize_t i0, i1, n0, n1 + cdef np.ndarray[double, ndim=2] exact, exact2, f_lin + + exact = self.exact + exact2 = self.exact2 + f_lin = self.freq_lin + n0 = exact.shape[0] + n1 = exact.shape[1] + + for i0 in xrange(n0): + for i1 in xrange(n1): + exact[i0, i1] = exp(-dt*f_lin[i0, i1]) + exact2[i0, i1] = exp(-dt/2*f_lin[i0, i1]) + self.dt_old = dt + + @cython.boundscheck(False) + @cython.wraparound(False) + def compute_ndim3_complex128(self, double dt): + cdef Py_ssize_t i0, i1, ik, nk, n0, n1 + cdef np.ndarray[DTYPEc_t, ndim=3] exact, exact2, f_lin + + nk = self.nk + n0 = self.n0 + n1 = self.n1 + exact = self.exact + exact2 = self.exact2 + f_lin = self.freq_lin + + for ik in xrange(nk): + for i0 in xrange(n0): + for i1 in xrange(n1): + exact[ik, i0, i1] = cexp(-dt*f_lin[ik, i0, i1]) + exact2[ik, i0, i1] = cexp(-dt/2*f_lin[ik, i0, i1]) + + self.dt_old = dt + + +class TimeSteppingPseudoSpectral(TimeSteppingPseudoSpectralPurePython): + + def _init_time_scheme(self): + + params_ts = self.params.time_stepping + + if params_ts.type_time_scheme not in ['RK2', 'RK4']: + raise ValueError('Problem name time_scheme') + + dtype = self.freq_lin.dtype + if dtype == np.float64: + str_type = 'float' + elif dtype == np.complex128: + str_type = 'complex' + else: + raise NotImplementedError('dtype of freq_lin:' + repr(dtype)) + + name_function = ( + '_time_step_' + params_ts.type_time_scheme + + '_state_ndim{}_freqlin_ndim{}_'.format( + self.sim.state.state_fft.data.ndim, self.freq_lin.ndim) + + str_type) + + if not hasattr(self, name_function): + raise NotImplementedError( + 'The function ' + name_function + + ' is not implemented.') + + exec('self._time_step_RK = self.' + name_function, + globals(), locals()) + + + @cython.embedsignature(True) + @cython.boundscheck(False) + @cython.wraparound(False) + def _time_step_RK4_state_ndim3_freqlin_ndim2_float(self): + """Advance in time *sim.state.state_fft* with the Runge-Kutta 4 method. + + See :ref:`the pure python RK4 function <rk4timescheme>` for the + presentation of the time scheme. + + For this function, the coefficient :math:`\sigma` is real and + represents the dissipation. + + """ + # cdef DTYPEf_t dt = self.deltat + cdef double dt = self.deltat + + cdef Py_ssize_t i0, i1, ik, nk, n0, n1 + + # cdef np.ndarray[DTYPEf_t, ndim=2] exact, exact2 + # This is strange, if I use DTYPEf_t and complex.h => bug + cdef np.ndarray[double, ndim=2] exact, exact2 + + cdef np.ndarray[DTYPEc_t, ndim=3] datas, datat + cdef np.ndarray[DTYPEc_t, ndim=3] datatemp, datatemp2 + + tendencies_nonlin = self.sim.tendencies_nonlin + state_fft = self.sim.state.state_fft + + nk = state_fft.data.shape[0] + n0 = state_fft.data.shape[1] + n1 = state_fft.data.shape[2] + + exact, exact2 = self.exact_linear_coefs.get_updated_coefs() + + tendencies_fft_1 = tendencies_nonlin() + + # # alternativelly, this + # state_fft_temp = (self.state_fft + dt/6*tendencies_fft_1)*exact + # state_fft_np12_approx1 = ( + # self.state_fft + dt/2*tendencies_fft_1)*exact2 + # # or this (slightly faster...) + + datas = state_fft.data + datat = tendencies_fft_1.data + + state_fft_temp = SetOfVariables(like_this_sov=state_fft) + datatemp = state_fft_temp.data + + state_fft_np12_approx1 = SetOfVariables(like_this_sov=state_fft) + datatemp2 = state_fft_np12_approx1.data + + for ik in xrange(nk): + for i0 in xrange(n0): + for i1 in xrange(n1): + datatemp[ik, i0, i1] = ( + datas[ik, i0, i1] + + dt/6*datat[ik, i0, i1])*exact[i0, i1] + datatemp2[ik, i0, i1] = ( + datas[ik, i0, i1] + + dt/2*datat[ik, i0, i1])*exact2[i0, i1] + + del(tendencies_fft_1) + tendencies_fft_2 = tendencies_nonlin(state_fft_np12_approx1) + del(state_fft_np12_approx1) + + # # alternativelly, this + # state_fft_temp += dt/3*exact2*tendencies_fft_2 + # state_fft_np12_approx2 = (exact2*self.state_fft + # + dt/2*tendencies_fft_2) + # # or this (slightly faster...) + + datat = tendencies_fft_2.data + + state_fft_np12_approx2 = SetOfVariables(like_this_sov=state_fft) + datatemp2 = state_fft_np12_approx2.data + + for ik in xrange(nk): + for i0 in xrange(n0): + for i1 in xrange(n1): + datatemp[ik, i0, i1] = ( + datatemp[ik, i0, i1] + + dt/3*exact2[i0, i1]*datat[ik, i0, i1]) + datatemp2[ik, i0, i1] = ( + exact2[i0, i1]*datas[ik, i0, i1] + + dt/2*datat[ik, i0, i1]) + + del(tendencies_fft_2) + tendencies_fft_3 = tendencies_nonlin(state_fft_np12_approx2) + del(state_fft_np12_approx2) + + # # alternativelly, this + # state_fft_temp += dt/3*exact2*tendencies_fft_3 + # state_fft_np1_approx = (exact*self.state_fft + # + dt*exact2*tendencies_fft_3) + # # or this (slightly faster...) + + datat = tendencies_fft_3.data + + state_fft_np1_approx = SetOfVariables(like_this_sov=state_fft) + datatemp2 = state_fft_np1_approx.data + + for ik in xrange(nk): + for i0 in xrange(n0): + for i1 in xrange(n1): + datatemp[ik, i0, i1] = ( + datatemp[ik, i0, i1] + + dt/3*exact2[i0, i1]*datat[ik, i0, i1]) + datatemp2[ik, i0, i1] = ( + exact[i0, i1]*datas[ik, i0, i1] + + dt*exact2[i0, i1]*datat[ik, i0, i1]) + + del(tendencies_fft_3) + tendencies_fft_4 = tendencies_nonlin(state_fft_np1_approx) + del(state_fft_np1_approx) + + # # alternativelly, this + # self.state_fft = state_fft_temp + dt/6*tendencies_fft_4 + # # or this (slightly faster... may be not...) + + datat = tendencies_fft_4.data + + for ik in xrange(nk): + for i0 in xrange(n0): + for i1 in xrange(n1): + datas[ik, i0, i1] = ( + datatemp[ik, i0, i1] + + dt/6*datat[ik, i0, i1]) + + @cython.embedsignature(True) + @cython.boundscheck(False) + @cython.wraparound(False) + def _time_step_RK4_state_ndim3_freqlin_ndim3_float(self): + """Advance in time *sim.state.state_fft* with the Runge-Kutta 4 method. + + See :ref:`the pure python RK4 function <rk4timescheme>` for the + presentation of the time scheme. + + For this function, the coefficient :math:`\sigma` is complex. + + """ + cdef double dt = self.deltat + cdef Py_ssize_t i0, i1, ik, nk, n0, n1 + cdef np.ndarray[double, ndim=3] exact, exact2 + cdef np.ndarray[DTYPEc_t, ndim=3] datas, datat + cdef np.ndarray[DTYPEc_t, ndim=3] datatemp, datatemp2 + + tendencies_nonlin = self.sim.tendencies_nonlin + + state_fft = self.sim.state.state_fft + datas = state_fft.data + nk = datas.shape[0] + n0 = datas.shape[1] + n1 = datas.shape[2] + + exact, exact2 = self.exact_linear_coefs.get_updated_coefs() + + tendencies_fft_1 = tendencies_nonlin() + + # # alternativelly, this + # state_fft_temp = (self.state_fft + dt/6*tendencies_fft_1)*exact + # state_fft_np12_approx1 = (self.state_fft + # + dt/2*tendencies_fft_1)*exact2 + # # or this (slightly faster...) + + datat = tendencies_fft_1.data + + state_fft_temp = SetOfVariables(like_this_sov=state_fft) + datatemp = state_fft_temp.data + + state_fft_np12_approx1 = SetOfVariables(like_this_sov=state_fft) + datatemp2 = state_fft_np12_approx1.data + + for ik in xrange(nk): + for i0 in xrange(n0): + for i1 in xrange(n1): + datatemp[ik, i0, i1] = ( + datas[ik, i0, i1] + + dt/6*datat[ik, i0, i1])*exact[ik, i0, i1] + datatemp2[ik, i0, i1] = ( + datas[ik, i0, i1] + + dt/2*datat[ik, i0, i1])*exact2[ik, i0, i1] + + del(tendencies_fft_1) + tendencies_fft_2 = tendencies_nonlin(state_fft_np12_approx1) + del(state_fft_np12_approx1) + + # # alternativelly, this + # state_fft_temp += dt/3*exact2*tendencies_fft_2 + # state_fft_np12_approx2 = (exact2*self.state_fft + # + dt/2*tendencies_fft_2) + # # or this (slightly faster...) + + datat = tendencies_fft_2.data + + state_fft_np12_approx2 = SetOfVariables(like_this_sov=state_fft) + datatemp2 = state_fft_np12_approx2.data + + for ik in xrange(nk): + for i0 in xrange(n0): + for i1 in xrange(n1): + datatemp[ik, i0, i1] = ( + datatemp[ik, i0, i1] + + dt/3*exact2[ik, i0, i1]*datat[ik, i0, i1]) + datatemp2[ik, i0, i1] = ( + exact2[ik, i0, i1]*datas[ik, i0, i1] + + dt/2*datat[ik, i0, i1]) + + del(tendencies_fft_2) + tendencies_fft_3 = tendencies_nonlin(state_fft_np12_approx2) + del(state_fft_np12_approx2) + + # # alternativelly, this + # state_fft_temp += dt/3*exact2*tendencies_fft_3 + # state_fft_np1_approx = (exact*self.state_fft + # + dt*exact2*tendencies_fft_3) + # # or this (slightly faster...) + + datat = tendencies_fft_3.data + + state_fft_np1_approx = SetOfVariables(like_this_sov=state_fft) + datatemp2 = state_fft_np1_approx.data + + for ik in xrange(nk): + for i0 in xrange(n0): + for i1 in xrange(n1): + datatemp[ik, i0, i1] = ( + datatemp[ik, i0, i1] + + dt/3*exact2[ik, i0, i1]*datat[ik, i0, i1]) + datatemp2[ik, i0, i1] = ( + exact[ik, i0, i1]*datas[ik, i0, i1] + + dt*exact2[ik, i0, i1]*datat[ik, i0, i1]) + + del(tendencies_fft_3) + tendencies_fft_4 = tendencies_nonlin(state_fft_np1_approx) + del(state_fft_np1_approx) + + # # alternativelly, this + # self.state_fft = state_fft_temp + dt/6*tendencies_fft_4 + # # or this (slightly faster... may be not...) + + datat = tendencies_fft_4.data + + for ik in xrange(nk): + for i0 in xrange(n0): + for i1 in xrange(n1): + datas[ik, i0, i1] = ( + datatemp[ik, i0, i1] + + dt/6*datat[ik, i0, i1]) + + + + def _time_step_RK2_state_ndim3_freqlin_ndim3_complex(self): + raise NotImplementedError + + @cython.embedsignature(True) + @cython.boundscheck(False) + @cython.wraparound(False) + def _time_step_RK4_state_ndim3_freqlin_ndim3_complex(self): + """Advance in time *sim.state.state_fft* with the Runge-Kutta 4 method. + + See :ref:`the pure python RK4 function <rk4timescheme>` for the + presentation of the time scheme. + + For this function, the coefficient :math:`\sigma` is complex. + + """ + cdef double dt = self.deltat + cdef Py_ssize_t i0, i1, ik, nk, n0, n1 + cdef np.ndarray[DTYPEc_t, ndim=3] exact, exact2 + cdef np.ndarray[DTYPEc_t, ndim=3] datas, datat + cdef np.ndarray[DTYPEc_t, ndim=3] datatemp, datatemp2 + + tendencies_nonlin = self.sim.tendencies_nonlin + + state_fft = self.sim.state.state_fft + datas = state_fft.data + nk = datas.shape[0] + n0 = datas.shape[1] + n1 = datas.shape[2] + + exact, exact2 = self.exact_linear_coefs.get_updated_coefs() + + tendencies_fft_1 = tendencies_nonlin() + + # # alternativelly, this + # state_fft_temp = (self.state_fft + dt/6*tendencies_fft_1)*exact + # state_fft_np12_approx1 = (self.state_fft + # + dt/2*tendencies_fft_1)*exact2 + # # or this (slightly faster...) + + datat = tendencies_fft_1.data + + state_fft_temp = SetOfVariables(like_this_sov=state_fft) + datatemp = state_fft_temp.data + + state_fft_np12_approx1 = SetOfVariables(like_this_sov=state_fft) + datatemp2 = state_fft_np12_approx1.data + + for ik in xrange(nk): + for i0 in xrange(n0): + for i1 in xrange(n1): + datatemp[ik, i0, i1] = ( + datas[ik, i0, i1] + + dt/6*datat[ik, i0, i1])*exact[ik, i0, i1] + datatemp2[ik, i0, i1] = ( + datas[ik, i0, i1] + + dt/2*datat[ik, i0, i1])*exact2[ik, i0, i1] + + del(tendencies_fft_1) + tendencies_fft_2 = tendencies_nonlin(state_fft_np12_approx1) + del(state_fft_np12_approx1) + + # # alternativelly, this + # state_fft_temp += dt/3*exact2*tendencies_fft_2 + # state_fft_np12_approx2 = (exact2*self.state_fft + # + dt/2*tendencies_fft_2) + # # or this (slightly faster...) + + datat = tendencies_fft_2.data + + state_fft_np12_approx2 = SetOfVariables(like_this_sov=state_fft) + datatemp2 = state_fft_np12_approx2.data + + for ik in xrange(nk): + for i0 in xrange(n0): + for i1 in xrange(n1): + datatemp[ik, i0, i1] = ( + datatemp[ik, i0, i1] + + dt/3*exact2[ik, i0, i1]*datat[ik, i0, i1]) + datatemp2[ik, i0, i1] = ( + exact2[ik, i0, i1]*datas[ik, i0, i1] + + dt/2*datat[ik, i0, i1]) + + del(tendencies_fft_2) + tendencies_fft_3 = tendencies_nonlin(state_fft_np12_approx2) + del(state_fft_np12_approx2) + + # # alternativelly, this + # state_fft_temp += dt/3*exact2*tendencies_fft_3 + # state_fft_np1_approx = (exact*self.state_fft + # + dt*exact2*tendencies_fft_3) + # # or this (slightly faster...) + + datat = tendencies_fft_3.data + + state_fft_np1_approx = SetOfVariables(like_this_sov=state_fft) + datatemp2 = state_fft_np1_approx.data + + for ik in xrange(nk): + for i0 in xrange(n0): + for i1 in xrange(n1): + datatemp[ik, i0, i1] = ( + datatemp[ik, i0, i1] + + dt/3*exact2[ik, i0, i1]*datat[ik, i0, i1]) + datatemp2[ik, i0, i1] = ( + exact[ik, i0, i1]*datas[ik, i0, i1] + + dt*exact2[ik, i0, i1]*datat[ik, i0, i1]) + + del(tendencies_fft_3) + tendencies_fft_4 = tendencies_nonlin(state_fft_np1_approx) + del(state_fft_np1_approx) + + # # alternativelly, this + # self.state_fft = state_fft_temp + dt/6*tendencies_fft_4 + # # or this (slightly faster... may be not...) + + datat = tendencies_fft_4.data + + for ik in xrange(nk): + for i0 in xrange(n0): + for i1 in xrange(n1): + datas[ik, i0, i1] = ( + datatemp[ik, i0, i1] + + dt/6*datat[ik, i0, i1]) diff --git a/fluidsim/operators/CySources/operators_cy.pyx b/fluidsim/operators/CySources/operators_cy.pyx new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL0N5U291cmNlcy9vcGVyYXRvcnNfY3kucHl4 --- /dev/null +++ b/fluidsim/operators/CySources/operators_cy.pyx @@ -0,0 +1,1606 @@ +""" +Numerical operators (:mod:`fluidsim.operators.operators`) +=============================================================== + +.. currentmodule:: fluidsim.operators.operators + +This module is written in Cython and provides the classes: + +.. autoclass:: Operators + :members: + :private-members: + +.. autoclass:: GridPseudoSpectral2D + :members: + :private-members: + +.. autoclass:: OperatorsPseudoSpectral2D + :members: + :private-members: + + +""" + +# DEF MPI4PY = 0 + +import sys + +cimport numpy as np +import numpy as np +np.import_array() + +try: + from mpi4py import MPI +except ImportError: + nb_proc = 1 + rank = 0 +else: + comm = MPI.COMM_WORLD + nb_proc = comm.size + rank = comm.Get_rank() + +IF MPI4PY: + from mpi4py cimport MPI + from mpi4py.mpi_c cimport * + + # solve an incompatibility between openmpi and mpi4py versions + cdef extern from 'mpi-compat.h': pass + + +from time import time, sleep +import datetime +import os +import matplotlib.pyplot as plt +import cython + +from libc.math cimport exp + +from fluidsim.operators.setofvariables import SetOfVariables + +from fluidsim.operators.fft import easypyfft + +# we define python and c types for physical and Fourier spaces +DTYPEb = np.uint8 +ctypedef np.uint8_t DTYPEb_t +DTYPEi = np.int +ctypedef np.int_t DTYPEi_t +DTYPEf = np.float64 +ctypedef np.float64_t DTYPEf_t +DTYPEc = np.complex128 +ctypedef np.complex128_t DTYPEc_t + +# Basically, you use the _t ones when you need to declare a type +# (e.g. cdef foo_t var, or np.ndarray[foo_t, ndim=...]. Ideally someday +# we won't have to make this distinction, but currently one is a C type +# and the other is a python object representing a numpy type (a dtype), +# and there's currently no way to identify the two without special +# compiler support. +# - Robert Bradshaw + + +cdef class Operators(object): + pass + + +cdef class GridPseudoSpectral2D(Operators): + """Describes a discretisation in spectral and spatial space. + + Parameters + ---------- + + nx, ny : int + Number of colocation points in the x and y directions + + Lx, Lx : float + Dimension of the numerical box in the x and y directions + + op_fft2d : :class:`FFT2Dmpi` + A instance of the class :class:`OP_FFT2Dmpi` + + SEQUENCIAL : bool + If True, the fft is sequencial even though ``nb_proc > 1`` + + """ + # number of nodes, sequenciel case + cdef public int nx_seq, ny_seq, nkx_seq, nky_seq + # number of nodes locally stored + cdef public int nx_loc, ny_loc, nkx_loc, nky_loc + cdef public DTYPEf_t Lx, Ly, deltax, deltay, + cdef public DTYPEf_t deltakx, deltaky, deltakh, kmax, kymax + + # shape of the arrays in the physical and Fourier spaces, + # for the sequential case: + cdef public np.ndarray shapeX_seq, shapeK_seq + # and for the parallel case: + cdef public np.ndarray shapeX_loc, shapeK_loc + # the names without loc or seq correspond to local (or general) quantities + cdef public np.ndarray shapeX, shapeK + # shape K when gathered: + cdef public np.ndarray shapeK_gat + + cdef public int idimx, idimy, idimkx, idimky + + # these names without loc or seq correspond to local quantities + cdef public np.ndarray XX, YY, RR, KX, KY, KK, K2, K4, K8 + cdef public np.ndarray kx_loc, ky_loc + cdef public np.ndarray x_seq, y_seq + + # the communicator, nb of processus and rank of the processus + IF MPI4PY: + cdef public MPI.Comm comm + cdef public int nb_proc, rank + cdef public int iX0loc_start, iKxloc_start, iKyloc_start + cdef public int nK0_loc, nK1_loc, dim_kx, dim_ky + cdef public np.ndarray iKxloc_start_rank + + cdef public DTYPEb_t TRANSPOSED, SEQUENCIAL + cdef public DTYPEb_t SAME_SIZE_IN_ALL_PROC + + # cdef public object where_is_wavenumber + + def __init__(self, int nx, int ny, + DTYPEf_t Lx=2*np.pi, DTYPEf_t Ly=2*np.pi, + op_fft2d=None, SEQUENCIAL=None): + if ny % 2 != 0 or nx % 2 != 0: + raise ValueError('conditions n0 and n1 even not fulfill') + + # n0 is ny and n1 is ny (see def of n0 and n1 in the fftw doc) + self.nx_seq = int(nx) + self.ny_seq = int(ny) + + self.Lx = np.float(Lx) + self.Ly = np.float(Ly) + + self.deltax = self.Lx/self.nx_seq + self.deltay = self.Ly/self.ny_seq + + self.x_seq = self.deltax * np.arange(self.nx_seq) + self.y_seq = self.deltay * np.arange(self.ny_seq) + + self.deltakx = 2*np.pi/self.Lx + self.deltaky = 2*np.pi/self.Ly + self.deltakh = self.deltakx + + self.nkx_seq = int(self.nx_seq/2.+1) + self.nky_seq = self.ny_seq + + self.kymax = self.deltaky*self.nky_seq/2. + + # info on MPI + self.nb_proc = nb_proc + self.rank = rank + if self.nb_proc > 1: + self.comm = comm + + self.shapeX_seq = np.array([self.ny_seq, self.nx_seq]) + self.shapeK_seq = np.array([self.nky_seq, self.nkx_seq]) + + if self.nb_proc == 1 or SEQUENCIAL: + self.SEQUENCIAL = True + self.SAME_SIZE_IN_ALL_PROC = True + self.shapeX = self.shapeX_seq + self.shapeK = self.shapeK_seq + self.shapeX_loc = self.shapeX_seq + self.shapeK_loc = self.shapeK_seq + self.shapeK_gat = self.shapeK_seq + + self.iX0loc_start = 0 + self.iKxloc_start = 0 + self.iKyloc_start = 0 + + self.nx_loc = self.nx_seq + self.ny_loc = self.ny_seq + self.nkx_loc = self.nkx_seq + self.nky_loc = self.nky_seq + + self.idimx = 1 + self.idimy = 0 + self.idimkx = 1 + self.idimky = 0 + + self.TRANSPOSED = False + + else: + + if nx/2+1 < self.nb_proc: + raise ValueError('condition nx/2+1 >= nb_proc not fulfill') + + self.SEQUENCIAL = False + if op_fft2d is None: + raise ValueError( + 'for parallel grid, init() needs a op_fft2d object') + if not hasattr(op_fft2d, 'shapeX_loc'): + raise ValueError( + 'The fft operator does not have "shapeX_loc", ' + 'which seems to indicate that it can not run with mpi.') + self.shapeK_gat = op_fft2d.shapeK_gat + self.shapeX_loc = op_fft2d.shapeX_loc + self.shapeK_loc = op_fft2d.shapeK_loc + self.shapeX = op_fft2d.shapeX_loc + self.shapeK = op_fft2d.shapeK_loc + + self.idimkx = op_fft2d.idimkx + self.idimky = op_fft2d.idimky + self.idimx = op_fft2d.idimx + self.idimy = op_fft2d.idimy + + self.nx_loc = self.shapeX_loc[self.idimx] + self.ny_loc = self.shapeX_loc[self.idimy] + self.nkx_loc = self.shapeK_loc[self.idimkx] + self.nky_loc = self.shapeK_loc[self.idimky] + self.iX0loc_start = op_fft2d.iX0loc_start + + self.iKxloc_start = op_fft2d.iKxloc_start + self.iKyloc_start = op_fft2d.iKyloc_start + + self.iKxloc_start_rank = np.array( + comm.allgather(self.iKxloc_start)) + + nkx_loc_rank = np.array(comm.allgather(self.nkx_loc)) + a = nkx_loc_rank + self.SAME_SIZE_IN_ALL_PROC = (a >= a.max()).all() + + self.TRANSPOSED = op_fft2d.TRANSPOSED + + self.nK0_loc = self.shapeK_loc[0] + self.nK1_loc = self.shapeK_loc[1] + + x_loc = self.deltax * np.arange(self.nx_loc) + y_loc = (self.deltay * + np.arange(self.iX0loc_start, self.iX0loc_start+self.ny_loc)) + [self.XX, self.YY] = np.meshgrid(x_loc, y_loc) + self.RR = np.sqrt((self.XX-self.Lx/2)**2 + (self.YY-self.Ly/2)**2) + + self.kx_loc = self.deltakx * np.arange(self.iKxloc_start, + self.iKxloc_start+self.nkx_loc) + self.ky_loc = self.deltaky * np.arange(self.iKyloc_start, + self.iKyloc_start+self.nky_loc) + self.ky_loc[self.ky_loc > self.kymax] = ( + self.ky_loc[self.ky_loc > self.kymax] + - 2*self.kymax) + + if not self.TRANSPOSED: + [self.KX, self.KY] = np.meshgrid(self.kx_loc, self.ky_loc) + self.dim_kx = 1 + self.dim_ky = 0 + else: + [self.KY, self.KX] = np.meshgrid(self.ky_loc, self.kx_loc) + self.dim_kx = 0 + self.dim_ky = 1 + + self.K2 = self.KX**2 + self.KY**2 + self.K4 = self.K2**2 + self.K8 = self.K4**2 + self.KK = np.sqrt(self.K2) + + self.kmax = np.sqrt((self.deltakx*self.nx_seq)**2 + + (self.deltaky*self.ny_seq)**2)/2 + + def where_is_wavenumber(self, kx_approx, ky_approx): + ikx_seq = np.round(kx_approx/self.deltakh) + + if ikx_seq >= self.nkx_seq: + raise ValueError('not good :-) ikx_seq >= self.nkx_seq') + + if self.SEQUENCIAL: + rank_k = 0 + ikx_loc = ikx_seq + else: + if self.SAME_SIZE_IN_ALL_PROC: + rank_k = int(np.floor(float(ikx_seq)/self.nkx_loc)) + else: + rank_k = 0 + while (rank_k < self.nb_proc-1 and + (not (self.iKxloc_start_rank[rank_k] <= ikx_seq + and ikx_seq < self.iKxloc_start_rank[rank_k+1]))): + rank_k += 1 + + ikx_loc = ikx_seq - self.iKxloc_start_rank[rank_k] + + iky_loc = np.round(ky_approx/self.deltaky) + if iky_loc < 0: + iky_loc = self.nky_loc+iky_loc + + if self.TRANSPOSED: + ik0_loc = ikx_loc + ik1_loc = iky_loc + else: + ik0_loc = iky_loc + ik1_loc = ikx_loc + + return rank_k, ik0_loc, ik1_loc + + +cdef class OperatorsPseudoSpectral2D(GridPseudoSpectral2D): + """Provides fast Fourier transform functions and 2D operators. + + `type_fft='FFTWCY'` : + cython wrapper of plans + fftw_plan_dft_r2c_2d / fftw_plan_dft_c2r_2d (sequencial case) + and + fftw_mpi_plan_dft_r2c_2d / fftw_mpi_plan_dft_c2r_2d + (parallel case) + + `type_fft='FFTWCCY'` : + cython wrapper of a self-written c libray using + sequencial fftw plans and MPI_Type. Seems to be faster than + the implementation of the mpi FFT by fftw (lib fftw-mpi). + + `type_fft='FFTWPY'` : + use of the module :mod:`easypyfft2D` with fftw + + `type_fft='FFTPACK' + use of the module :mod:`easypyfft2D` with fftp + (bad and slow implementation!) + + """ + + cdef public DTYPEf_t coef_dealiasing + cdef public object fft2, ifft2 + cdef public object gather_Xspace, gather_Kspace, + cdef public object scatter_Xspace, scatter_Kspace + cdef public object project_fft_on_realX + cdef public object params + cdef public np.ndarray K2_not0, K4_not0, KX_over_K2, KY_over_K2 + cdef public np.ndarray Kappa2, Kappa_over_ic, f_over_c2Kappa2 + cdef public np.ndarray where_dealiased + + cdef public int nkxE, nkyE, nkhE + cdef public np.ndarray kxE, kyE, khE + + cdef public str type_fft + + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + if nb_proc > 1: + type_fft = 'FFTWCCY' + else: + if not sys.platform == 'win32': + type_fft = 'FFTWCY' + else: + type_fft = 'FFTWPY' + + attribs = {'type_fft': type_fft, + 'TRANSPOSED_OK': True, + 'coef_dealiasing': 2./3, + 'nx': 48, + 'ny': 48, + 'Lx': 8, + 'Ly': 8} + params.set_child('oper', attribs=attribs) + + def __init__(self, + SEQUENCIAL=None, + params=None, + goal_to_print=None): + + if not params.ONLY_COARSE_OPER: + nx = int(params.oper.nx) + ny = int(params.oper.ny) + else: + nx = 4 + ny = 4 + + Lx = params.oper.Lx + Ly = params.oper.Ly + type_fft = str(params.oper.type_fft) + coef_dealiasing = params.oper.coef_dealiasing + TRANSPOSED = params.oper.TRANSPOSED_OK + + if rank == 0: + to_print = 'Init. operator' + if goal_to_print is not None: + to_print += ' ('+goal_to_print+')' + print(to_print) + + if params is not None: + self.params = params + + list_type_fft = ['FFTWCY', 'FFTWCCY', 'FFTWPY', 'FFTPACK'] + if type_fft not in list_type_fft: + raise ValueError('type_fft should be in '+repr(list_type_fft)) + + if type_fft == 'FFTWCCY' and nb_proc == 1: + type_fft = 'FFTWCY' + + try: + if type_fft == 'FFTWCY': + import fluidsim.operators.fft.fftw2dmpicy as fftw2Dmpi + elif type_fft == 'FFTWCCY': + import fluidsim.operators.fft.fftw2dmpiccy as fftw2Dmpi + except ImportError as err: + print('ImportError for fftw2Dmpicy and fftw2Dmpiccy') + type_fft = 'FFTWPY' + if nb_proc > 1 and SEQUENCIAL is None: + raise ValueError( + 'if nb_proc>1, we need to use one of this library') + if type_fft == 'FFTWPY': + try: + import pyfftw + except ImportError as err: + print('ImportError for fftw3, we use fftpack (very slow)') + type_fft = 'FFTPACK' + + self.type_fft = type_fft + + # Initialization of the fft transforms + if not (type_fft == 'FFTWPY' or type_fft == 'FFTPACK'): + if not TRANSPOSED and type_fft == 'FFTWCCY': + raise ValueError('FFTWCCY does not suport the ' + '(inefficient!) option TRANSPOSED=False') + + if type_fft == 'FFTWCY': + op_fft2d = fftw2Dmpi.FFT2Dmpi(ny, nx, + TRANSPOSED=TRANSPOSED, + SEQUENCIAL=SEQUENCIAL) + else: + op_fft2d = fftw2Dmpi.FFT2Dmpi(ny, nx) + if op_fft2d.nb_proc > 1: + self.gather_Xspace = op_fft2d.gather_Xspace + self.gather_Kspace = op_fft2d.gather_Kspace + self.scatter_Xspace = op_fft2d.scatter_Xspace + self.scatter_Kspace = op_fft2d.scatter_Kspace + + elif type_fft == 'FFTWPY': + op_fft2d = easypyfft.FFTW2DReal2Complex(nx, ny) + elif type_fft == 'FFTPACK': + op_fft2d = easypyfft.fftp2D(nx, ny) + + self.fft2 = op_fft2d.fft2d + self.ifft2 = op_fft2d.ifft2d + + GridPseudoSpectral2D.__init__(self, nx, ny, Lx, Ly, + op_fft2d=op_fft2d, SEQUENCIAL=SEQUENCIAL) + + self.K2_not0 = self.K2.copy() + self.K4_not0 = self.K4.copy() + if rank == 0 or SEQUENCIAL: + self.K2_not0[0, 0] = 10.e-10 + self.K4_not0[0, 0] = 10.e-10 + + self.KX_over_K2 = self.KX/self.K2_not0 + self.KY_over_K2 = self.KY/self.K2_not0 + + try: + self.Kappa2 = self.K2 + self.params.kd2 + + self.Kappa_over_ic = -1.j*np.sqrt( + self.Kappa2/self.params.c2 + ) + + if self.params.f != 0: + self.f_over_c2Kappa2 = self.params.f/( + self.params.c2*self.Kappa2 + ) + + except AttributeError: + pass + + # for spectra, we forget the larger wavenumber, + # since there is no energy inside because of dealiasing + self.nkxE = self.nkx_seq - 1 + self.nkyE = self.nky_seq/2 + + self.kxE = self.deltakx * np.arange(self.nkxE) + self.kyE = self.deltaky * np.arange(self.nkyE) + self.khE = self.kxE + self.nkhE = self.nkxE + + # Initialisation dealiasing + self.coef_dealiasing = coef_dealiasing + CONDKX = abs(self.KX) > self.coef_dealiasing*self.kxE.max() + CONDKY = abs(self.KY) > self.coef_dealiasing*self.kyE.max() + where_dealiased = np.logical_or(CONDKX, CONDKY) + + self.where_dealiased = np.array(where_dealiased, dtype=DTYPEb) + + try: + self.project_fft_on_realX = op_fft2d.project_fft_on_realX + except KeyError: + if nb_proc > 1: + raise ValueError( + 'nb_proc > 1 but no function' + 'project_fft_on_realX defined') + self.project_fft_on_realX = self.project_fft_on_realX_seq + + def produce_str_describing_oper(self): + """Produce a string describing the operator.""" + if (self.Lx/np.pi).is_integer(): + str_Lx = repr(int(self.Lx/np.pi)) + 'pi' + else: + str_Lx = '{:.3f}'.format(self.Lx).rstrip('0') + if (self.Ly/np.pi).is_integer(): + str_Ly = repr(int(self.Ly/np.pi)) + 'pi' + else: + str_Ly = '{:.3f}'.format(self.Ly).rstrip('0') + return ('L='+str_Lx+'x'+str_Ly+'_{}x{}').format( + self.nx_seq, self.ny_seq) + + def produce_long_str_describing_oper(self): + """Produce a string describing the operator.""" + if (self.Lx/np.pi).is_integer(): + str_Lx = repr(int(self.Lx/np.pi)) + '*pi' + else: + str_Lx = '{:.3f}'.format(self.Lx).rstrip('0') + if (self.Ly/np.pi).is_integer(): + str_Ly = repr(int(self.Ly/np.pi)) + '*pi' + else: + str_Ly = '{:.3f}'.format(self.Ly).rstrip('0') + return ( + 'nx = {0:6d} ; ny = {1:6d}\n'.format(self.nx_seq, self.ny_seq) + + 'Lx = ' + str_Lx + ' ; Ly = ' + str_Ly + '\n') + + # def rotfft_from_vecfft(self, vecx_fft, vecy_fft): + # """Return the rotational (curl) of a vector in spectral space.""" + # return 1j*( self.KX*vecy_fft - self.KY*vecx_fft ) + + @cython.boundscheck(False) + @cython.wraparound(False) + def rotfft_from_vecfft(self, + np.ndarray[DTYPEc_t, ndim=2] vecx_fft, + np.ndarray[DTYPEc_t, ndim=2] vecy_fft): + """Return the rotational of a vector in spectral space.""" + cdef Py_ssize_t i0, i1, n0, n1 + cdef np.ndarray[DTYPEc_t, ndim=2] rot_fft + cdef np.ndarray[DTYPEf_t, ndim=2] KX, KY + + n0 = self.nK0_loc + n1 = self.nK1_loc + + KX = self.KX + KY = self.KY + rot_fft = np.empty([n0, n1], dtype=np.complex128) + + for i0 in range(n0): + for i1 in range(n1): + rot_fft[i0, i1] = 1j*(KX[i0, i1]*vecy_fft[i0, i1] + - KY[i0, i1]*vecx_fft[i0, i1]) + return rot_fft + + # def divfft_from_vecfft_old(self, vecx_fft, vecy_fft): + # """Return the divergence of a vector in spectral space.""" + # return 1j*( self.KX*vecx_fft + self.KY*vecy_fft ) + + @cython.boundscheck(False) + @cython.wraparound(False) + def divfft_from_vecfft(self, + np.ndarray[DTYPEc_t, ndim=2] vecx_fft, + np.ndarray[DTYPEc_t, ndim=2] vecy_fft): + """Return the divergence of a vector in spectral space.""" + cdef Py_ssize_t i0, i1, n0, n1 + cdef np.ndarray[DTYPEc_t, ndim=2] div_fft + cdef np.ndarray[DTYPEf_t, ndim=2] KX, KY + + n0 = self.nK0_loc + n1 = self.nK1_loc + + KX = self.KX + KY = self.KY + div_fft = np.empty([n0, n1], dtype=np.complex128) + + for i0 in xrange(n0): + for i1 in xrange(n1): + div_fft[i0, i1] = 1j*(KX[i0, i1]*vecx_fft[i0, i1] + + KY[i0, i1]*vecy_fft[i0, i1] + ) + return div_fft + + def vecfft_from_rotfft(self, rot_fft): + """Return the velocity in spectral space computed from the + rotational.""" + ux_fft = 1j * self.KY_over_K2*rot_fft + uy_fft = -1j * self.KX_over_K2*rot_fft + return ux_fft, uy_fft + + def vecfft_from_divfft(self, div_fft): + """Return the velocity in spectral space computed from the + divergence.""" + ux_fft = -1j * self.KX_over_K2*div_fft + uy_fft = -1j * self.KY_over_K2*div_fft + return ux_fft, uy_fft + + + + + # def gradfft_from_fft_old(self, f_fft): + # """Return the gradient of f_fft in spectral space.""" + # px_f_fft = 1j * self.KX*f_fft + # py_f_fft = 1j * self.KY*f_fft + # return px_f_fft, py_f_fft + + + @cython.boundscheck(False) + @cython.wraparound(False) + def pxffft_from_fft(self, f_fft): + """Return the gradient of f_fft in spectral space.""" + cdef Py_ssize_t i0, i1, n0, n1 + cdef np.ndarray[DTYPEf_t, ndim=2] KX + cdef np.ndarray[DTYPEc_t, ndim=2] px_f_fft + + cdef np.ndarray[DTYPEc_t, ndim=2] fc_fft + cdef np.ndarray[DTYPEf_t, ndim=2] ff_fft + + n0 = self.nK0_loc + n1 = self.nK1_loc + + KX = self.KX + KY = self.KY + + px_f_fft = np.empty([n0, n1], dtype=np.complex128) + + if f_fft.dtype == np.float64: + ff_fft = f_fft + for i0 in xrange(n0): + for i1 in xrange(n1): + px_f_fft[i0, i1] = 1j * KX[i0, i1]*ff_fft[i0, i1] + else: + fc_fft = f_fft + for i0 in xrange(n0): + for i1 in xrange(n1): + px_f_fft[i0, i1] = 1j * KX[i0, i1]*fc_fft[i0, i1] + + return px_f_fft + + @cython.boundscheck(False) + @cython.wraparound(False) + def gradfft_from_fft(self, f_fft): + """Return the gradient of f_fft in spectral space.""" + cdef Py_ssize_t i0, i1, n0, n1 + cdef np.ndarray[DTYPEf_t, ndim=2] KX, KY + cdef np.ndarray[DTYPEc_t, ndim=2] px_f_fft, py_f_fft + + cdef np.ndarray[DTYPEc_t, ndim=2] fc_fft + cdef np.ndarray[DTYPEf_t, ndim=2] ff_fft + + n0 = self.nK0_loc + n1 = self.nK1_loc + + KX = self.KX + KY = self.KY + + px_f_fft = np.empty([n0, n1], dtype=np.complex128) + py_f_fft = np.empty([n0, n1], dtype=np.complex128) + + if f_fft.dtype == np.float64: + ff_fft = f_fft + for i0 in xrange(n0): + for i1 in xrange(n1): + px_f_fft[i0, i1] = 1j * KX[i0, i1]*ff_fft[i0, i1] + py_f_fft[i0, i1] = 1j * KY[i0, i1]*ff_fft[i0, i1] + else: + fc_fft = f_fft + for i0 in xrange(n0): + for i1 in xrange(n1): + px_f_fft[i0, i1] = 1j * KX[i0, i1]*fc_fft[i0, i1] + py_f_fft[i0, i1] = 1j * KY[i0, i1]*fc_fft[i0, i1] + + return px_f_fft, py_f_fft + + def projection_perp(self, fx_fft, fy_fft): + KX = self.KX + KY = self.KY + a = fx_fft - self.KX_over_K2*(KX*fx_fft+KY*fy_fft) + b = fy_fft - self.KY_over_K2*(KX*fx_fft+KY*fy_fft) + fx_fft[:] = a + fy_fft[:] = b + return a, b + + def uxuyfft_from_psifft(self, psi_fft): + px_psi_fft, py_psi_fft = self.gradfft_from_fft(psi_fft) + ux_fft = -py_psi_fft + uy_fft = px_psi_fft + return ux_fft, uy_fft + + def rotfft_from_psifft(self, psi_fft): + rot_fft = -self.K2*psi_fft + return rot_fft + + def uxuyetafft_from_qfft(self, q_fft, params=None): + """Compute ux, uy and eta in Fourier space.""" + if params is None: + params = self.params + K2 = self.K2 + K2_not0 = self.K2_not0 + rot_fft = K2*q_fft/(K2_not0+params.kd2) + if rank == 0: + rot_fft[0, 0] = 0. + ux_fft, uy_fft = self.vecfft_from_rotfft(rot_fft) + + if params.f == 0: + eta_fft = self.constant_arrayK(value=0) + else: + eta_fft = -params.f*q_fft/(K2_not0+params.kd2)/params.c2 + if rank == 0: + eta_fft[0, 0] = 0. + return ux_fft, uy_fft, eta_fft + + def uxuyetafft_from_afft(self, a_fft, params=None): + """Compute ux, uy and eta in Fourier space.""" + if params is None: + params = self.params + K2 = self.K2 + K2_not0 = self.K2_not0 + + if params.f == 0: + rot_fft = self.constant_arrayK(value=0) + else: + rot_fft = params.f*a_fft/(K2_not0+params.kd2) + if rank == 0: + rot_fft[0, 0] = 0. + ux_fft, uy_fft = self.vecfft_from_rotfft(rot_fft) + + eta_fft = a_fft/(K2_not0+params.kd2) + if rank == 0: + eta_fft[0, 0] = 0. + return ux_fft, uy_fft, eta_fft + + def rotfft_from_qfft(self, q_fft, params=None): + """Compute ux, uy and eta in Fourier space.""" + if params is None: + params = self.params + K2 = self.K2 + K2_not0 = self.K2_not0 + rot_fft = K2*q_fft/(K2_not0+params.kd2) + if rank == 0: + rot_fft[0, 0] = 0. + return rot_fft + + def rotfft_from_afft(self, a_fft, params=None): + """Compute ux, uy and eta in Fourier space.""" + if params is None: + params = self.params + K2 = self.K2 + K2_not0 = self.K2_not0 + if params.f == 0: + rot_fft = self.constant_arrayK(value=0) + else: + rot_fft = params.f*a_fft/(K2_not0+params.kd2) + if rank == 0: + rot_fft[0, 0] = 0. + return rot_fft + + def afft_from_uxuyetafft(self, ux_fft, uy_fft, eta_fft, + params=None): + if params is None: + params = self.params + rot_fft = self.rotfft_from_vecfft(ux_fft, uy_fft) + a_fft = self.K2*eta_fft + if params.f != 0: + a_fft += params.f/params.c2*rot_fft + return a_fft + + def etafft_from_qfft(self, q_fft, params=None): + """Compute eta in Fourier space.""" + if params is None: + params = self.params + K2_not0 = self.K2_not0 + if params.f == 0: + eta_fft = self.constant_arrayK(value=0) + else: + eta_fft = -params.f/params.c2*q_fft/(K2_not0+params.kd2) + if rank == 0: + eta_fft[0, 0] = 0. + return eta_fft + + def etafft_from_afft(self, a_fft, params=None): + """Compute eta in Fourier space.""" + if params is None: + params = self.params + K2_not0 = self.K2_not0 + eta_fft = a_fft/(K2_not0+params.kd2) + if rank == 0: + eta_fft[0, 0] = 0. + return eta_fft + + def etafft_from_aqfft(self, a_fft, q_fft, params=None): + """Compute eta in Fourier space.""" + if params is None: + params = self.params + K2_not0 = self.K2_not0 + if params.f == 0: + eta_fft = a_fft/K2_not0 + else: + eta_fft = (a_fft - params.f/params.c2*q_fft)/( + K2_not0+params.kd2) + if rank == 0: + eta_fft[0, 0] = 0. + return eta_fft + + def qdafft_from_uxuyetafft(self, ux_fft, uy_fft, eta_fft, params=None): + if params is None: + params = self.params + div_fft = self.divfft_from_vecfft(ux_fft, uy_fft) + rot_fft = self.rotfft_from_vecfft(ux_fft, uy_fft) + q_fft = rot_fft - params.f*eta_fft + ageo_fft = params.f/params.c2*rot_fft + self.K2*eta_fft + return q_fft, div_fft, ageo_fft + + def apamfft_from_adfft(self, a_fft, d_fft): + """Return the engein modes ap and am.""" + Delta_a_fft = self.Kappa_over_ic*d_fft + ap_fft = 0.5*(a_fft + Delta_a_fft) + am_fft = 0.5*(a_fft - Delta_a_fft) + return ap_fft, am_fft + + @cython.boundscheck(False) + @cython.wraparound(False) + def divfft_from_apamfft(self, ap_fft, am_fft): + """Return div from the engein modes ap and am.""" + cdef Py_ssize_t i0, i1, n0, n1 + cdef Py_ssize_t rank = self.rank + cdef np.ndarray[DTYPEc_t, ndim=2] Kappa_over_ic, Delta_a_fft + cdef np.ndarray[DTYPEc_t, ndim=2] d_fft + + Delta_a_fft = ap_fft - am_fft + n0 = self.nK0_loc + n1 = self.nK1_loc + Kappa_over_ic = self.Kappa_over_ic + d_fft = np.empty([n0, n1], dtype=np.complex128) + + for i0 in range(n0): + for i1 in range(n1): + if i0 == 0 and i1 == 0 and rank == 0: + d_fft[i0, i1] = 0. + else: + d_fft[i0, i1] = ( + Delta_a_fft[i0, i1]/Kappa_over_ic[i0, i1] + ) + return d_fft + + def qapamfft_from_uxuyetafft_old(self, ux_fft, uy_fft, eta_fft, + params=None): + """ux, uy, eta (fft) ---> q, ap, am (fft)""" + if params is None: + params = self.params + div_fft = self.divfft_from_vecfft(ux_fft, uy_fft) + rot_fft = self.rotfft_from_vecfft(ux_fft, uy_fft) + q_fft = rot_fft - params.f*eta_fft + a_fft = (self.K2*eta_fft + + params.f/params.c2*rot_fft) + ap_fft, am_fft = self.apamfft_from_adfft(a_fft, div_fft) + if rank == 0: + ap_fft[0, 0] = ux_fft[0, 0] + 1.j*uy_fft[0, 0] + am_fft[0, 0] = ux_fft[0, 0] - 1.j*uy_fft[0, 0] + return q_fft, ap_fft, am_fft + + @cython.boundscheck(False) + @cython.wraparound(False) + def qapamfft_from_uxuyetafft(self, + np.ndarray[DTYPEc_t, ndim=2] ux_fft, + np.ndarray[DTYPEc_t, ndim=2] uy_fft, + np.ndarray[DTYPEc_t, ndim=2] eta_fft, + params=None): + """ux, uy, eta (fft) ---> q, ap, am (fft)""" + cdef Py_ssize_t i0, i1, n0, n1 + cdef Py_ssize_t rank = self.rank + cdef np.ndarray[DTYPEf_t, ndim=2] KX, KY, K2 + cdef np.ndarray[DTYPEc_t, ndim=2] Kappa_over_ic + cdef np.ndarray[DTYPEc_t, ndim=2] q_fft, ap_fft, am_fft + cdef DTYPEc_t rot_fft, a_over2_fft, Deltaa_over2_fft + cdef DTYPEf_t freq_Corio, f_over_c2 + + if params is None: + params = self.params + + n0 = self.nK0_loc + n1 = self.nK1_loc + + KX = self.KX + KY = self.KY + K2 = self.K2 + Kappa_over_ic = self.Kappa_over_ic + KX_over_K2 = self.KX_over_K2 + KY_over_K2 = self.KY_over_K2 + + q_fft = np.empty([n0, n1], dtype=np.complex128) + ap_fft = np.empty([n0, n1], dtype=np.complex128) + am_fft = np.empty([n0, n1], dtype=np.complex128) + + freq_Corio = params.f + f_over_c2 = freq_Corio/params.c2 + + if freq_Corio != 0: + for i0 in xrange(n0): + for i1 in xrange(n1): + if i0 == 0 and i1 == 0 and rank == 0: + q_fft[i0, i1] = 0 + ap_fft[i0, i1] = ux_fft[0, 0] + 1.j*uy_fft[0, 0] + am_fft[i0, i1] = ux_fft[0, 0] - 1.j*uy_fft[0, 0] + else: + + rot_fft = 1j*( + KX[i0, i1]*uy_fft[i0, i1] - + KY[i0, i1]*ux_fft[i0, i1]) + + q_fft[i0, i1] = rot_fft - freq_Corio*eta_fft[i0, i1] + + a_over2_fft = 0.5*( + K2[i0, i1] * eta_fft[i0, i1] + + f_over_c2*rot_fft) + + Deltaa_over2_fft = 0.5j*Kappa_over_ic[i0, i1]*( + KX[i0, i1]*ux_fft[i0, i1] + + KY[i0, i1]*uy_fft[i0, i1]) + + ap_fft[i0, i1] = a_over2_fft + Deltaa_over2_fft + am_fft[i0, i1] = a_over2_fft - Deltaa_over2_fft + + else: # (freq_Corio == 0.) + for i0 in xrange(n0): + for i1 in xrange(n1): + if i0 == 0 and i1 == 0 and rank == 0: + q_fft[i0, i1] = 0 + ap_fft[i0, i1] = ux_fft[0, 0] + 1.j*uy_fft[0, 0] + am_fft[i0, i1] = ux_fft[0, 0] - 1.j*uy_fft[0, 0] + else: + q_fft[i0, i1] = 1j*( + KX[i0, i1]*uy_fft[i0, i1] + - KY[i0, i1]*ux_fft[i0, i1]) + + a_over2_fft = 0.5*K2[i0, i1]*eta_fft[i0, i1] + + Deltaa_over2_fft = 0.5j*Kappa_over_ic[i0, i1]*( + KX[i0, i1]*ux_fft[i0, i1] + + KY[i0, i1]*uy_fft[i0, i1]) + + ap_fft[i0, i1] = a_over2_fft + Deltaa_over2_fft + am_fft[i0, i1] = a_over2_fft - Deltaa_over2_fft + + return q_fft, ap_fft, am_fft + + def uxuyetafft_from_qapamfft_old(self, q_fft, ap_fft, am_fft): + """q, ap, am (fft) ---> ux, uy, eta (fft)""" + a_fft = ap_fft + am_fft + if rank == 0: + a_fft[0, 0] = 0. + div_fft = self.divfft_from_apamfft(ap_fft, am_fft) + (uxa_fft, uya_fft, etaa_fft + ) = self.uxuyetafft_from_afft(a_fft) + (uxq_fft, uyq_fft, etaq_fft + ) = self.uxuyetafft_from_qfft(q_fft) + uxd_fft, uyd_fft = self.vecfft_from_divfft(div_fft) + ux_fft = uxa_fft + uxq_fft + uxd_fft + uy_fft = uya_fft + uyq_fft + uyd_fft + eta_fft = etaa_fft + etaq_fft + if rank == 0: + ux_fft[0, 0] = 0.5 * (ap_fft[0, 0] + am_fft[0, 0]) + uy_fft[0, 0] = 0.5j * (am_fft[0, 0] - ap_fft[0, 0]) + return ux_fft, uy_fft, eta_fft + + # @cython.boundscheck(False) + # @cython.wraparound(False) + def uxuyetafft_from_qapamfft(self, + np.ndarray[DTYPEc_t, ndim=2] q_fft, + np.ndarray[DTYPEc_t, ndim=2] ap_fft, + np.ndarray[DTYPEc_t, ndim=2] am_fft, + params=None): + """q, ap, am (fft) ---> ux, uy, eta (fft)""" + cdef Py_ssize_t i0, i1, n0, n1 + cdef Py_ssize_t rank = self.rank + cdef np.ndarray[DTYPEf_t, ndim=2] KX, KY, K2 + cdef np.ndarray[DTYPEc_t, ndim=2] Kappa_over_ic + cdef np.ndarray[DTYPEf_t, ndim=2] Kappa2 + cdef np.ndarray[DTYPEf_t, ndim=2] f_over_c2Kappa2 + cdef np.ndarray[DTYPEf_t, ndim=2] KX_over_K2, KY_over_K2 + cdef np.ndarray[DTYPEc_t, ndim=2] eta_fft, ux_fft, uy_fft + cdef DTYPEc_t div_fft, rot_fft + cdef DTYPEf_t freq_Corio + + if params is None: + params = self.params + + n0 = self.nK0_loc + n1 = self.nK1_loc + + KX = self.KX + KY = self.KY + K2 = self.K2 + Kappa2 = self.Kappa2 + Kappa_over_ic = self.Kappa_over_ic + f_over_c2Kappa2 = self.f_over_c2Kappa2 + KX_over_K2 = self.KX_over_K2 + KY_over_K2 = self.KY_over_K2 + + eta_fft = np.empty([n0, n1], dtype=np.complex128) + ux_fft = np.empty([n0, n1], dtype=np.complex128) + uy_fft = np.empty([n0, n1], dtype=np.complex128) + + freq_Corio = params.f + + if freq_Corio != 0: + for i0 in xrange(n0): + for i1 in xrange(n1): + if rank == 0 and i0 == 0 and i1 == 0: + eta_fft[i0, i1] = 0 + ux_fft[i0, i1] = 0.5 * (ap_fft[0, 0] + am_fft[0, 0]) + uy_fft[i0, i1] = 0.5j * (am_fft[0, 0] - ap_fft[0, 0]) + else: + div_fft = ( + ap_fft[i0, i1] - am_fft[i0, i1] + )/Kappa_over_ic[i0, i1] + eta_fft[i0, i1] = ( + (ap_fft[i0, i1] + am_fft[i0, i1])/Kappa2[i0, i1] + - f_over_c2Kappa2[i0, i1]*q_fft[i0, i1]) + rot_fft = ( + q_fft[i0, i1] + + freq_Corio*eta_fft[i0, i1]) + ux_fft[i0, i1] = ( + 1j * KY_over_K2[i0, i1]*rot_fft + - 1j * KX_over_K2[i0, i1]*div_fft) + uy_fft[i0, i1] = ( + -1j * KX_over_K2[i0, i1]*rot_fft + - 1j * KY_over_K2[i0, i1]*div_fft) + + else: # (freq_Corio == 0.) + for i0 in xrange(n0): + for i1 in xrange(n1): + if i0 == 0 and i1 == 0 and rank == 0: + eta_fft[i0, i1] = 0 + ux_fft[i0, i1] = 0.5 * (ap_fft[0, 0] + am_fft[0, 0]) + uy_fft[i0, i1] = 0.5j * (am_fft[0, 0] - ap_fft[0, 0]) + else: + div_fft = ( + ap_fft[i0, i1] - am_fft[i0, i1] + )/Kappa_over_ic[i0, i1] + eta_fft[i0, i1] = ( + ap_fft[i0, i1] + am_fft[i0, i1] + )/K2[i0, i1] + rot_fft = q_fft[i0, i1] + ux_fft[i0, i1] = ( + 1j*KY_over_K2[i0, i1]*rot_fft + - 1j*KX_over_K2[i0, i1]*div_fft) + uy_fft[i0, i1] = ( + - 1j*KX_over_K2[i0, i1]*rot_fft + - 1j*KY_over_K2[i0, i1]*div_fft) + + return ux_fft, uy_fft, eta_fft + + def dealiasing(self, *arguments): + for ii in range(len(arguments)): + thing = arguments[ii] + if isinstance(thing, np.ndarray): + loopKdealiasing(thing, self.where_dealiased, + self.nK0_loc, self.nK1_loc) + elif isinstance(thing, SetOfVariables): + thing.dealiasing(self.where_dealiased) + + # def sum_wavenumbers_old(self, field_fft): + # S_allkx = np.sum(field_fft) + # if not self.TRANSPOSED: + # S_kx0 = np.sum( field_fft[:,0] ) + # else: + # if self.rank==0: + # S_kx0 = np.sum( field_fft[0,:] ) + # else: + # S_kx0 = 0. + # S_result = 2*S_allkx-S_kx0 + # if self.nb_proc>1: + # S_result = self.comm.allreduce(S_result, op=MPI.SUM) + # return S_result + + def mean_space(self, field): + + mean_field = np.mean(field) + if not self.SEQUENCIAL: + mean_field = self.comm.allreduce(mean_field, op=MPI.SUM) + mean_field /= nb_proc + return mean_field + + def sum_wavenumbers(self, np.ndarray[DTYPEf_t, ndim=2] A_fft): + """Sum the given array over all wavenumbers.""" + cdef np.uint32_t ikO, ik1 + cdef np.uint32_t nk0loc, nk1loc, rank, TRANSPOSED + cdef DTYPEf_t A0D, sum_A_fft + + nk0loc = self.shapeK_loc[0] + nk1loc = self.shapeK_loc[1] + + rank = self.rank + + if self.TRANSPOSED: + TRANSPOSED = 1 + else: + TRANSPOSED = 0 + + sum_A_fft = 0. + + for ik0 in range(nk0loc): + for ik1 in range(nk1loc): + A0D = A_fft[ik0, ik1] + if TRANSPOSED == 0: + if ik1 > 0: + A0D = A0D*2 + else: + if ik0 > 0 or rank > 0: + A0D = A0D*2 + sum_A_fft += A0D + + # if self.nb_proc>1: + if not self.SEQUENCIAL: + sum_A_fft = self.comm.allreduce(sum_A_fft, op=MPI.SUM) + return sum_A_fft + + def spectra1D_from_fft(self, energy_fft): + """Compute the 1D spectra. Return a dictionary.""" + if self.nb_proc == 1: + # In this case, self.dim_ky==0 and self.dim_ky==1 + # Memory is not shared + # note that only the kx>=0 are in the spectral variables + # to obtain the spectrum as a function of kx + # we sum over all ky + # the 2 is here because there are only the kx>=0 + E_kx = 2.*energy_fft.sum(self.dim_ky)/self.deltakx + E_kx[0] = E_kx[0]/2 + E_kx = E_kx[:self.nkxE] + # computation of E_ky + E_ky_temp = energy_fft[:, 0].copy() + E_ky_temp += 2*energy_fft[:, 1:].sum(1) + nkyE = self.nkyE + E_ky = E_ky_temp[0:nkyE] + E_ky[1:nkyE] = E_ky[1:nkyE] + E_ky_temp[self.nky_seq:nkyE:-1] + E_ky = E_ky/self.deltaky + + elif self.TRANSPOSED: + # In this case, self.dim_ky==1 and self.dim_ky==0 + # Memory is shared along kx + # note that only the kx>=0 are in the spectral variables + # to obtain the spectrum as a function of kx + # we sum er.mamover all ky + # the 2 is here because there are only the kx>=0 + E_kx_loc = 2.*energy_fft.sum(self.dim_ky)/self.deltakx + if self.rank == 0: + E_kx_loc[0] = E_kx_loc[0]/2 + E_kx = np.empty(self.nkxE) + counts = self.comm.allgather(self.nkx_loc) + self.comm.Allgatherv(sendbuf=[E_kx_loc, MPI.DOUBLE], + recvbuf=[E_kx, (counts, None), MPI.DOUBLE]) + E_kx = E_kx[:self.nkxE] + # computation of E_ky + if self.rank == 0: + E_ky_temp = energy_fft[0, :]+2*energy_fft[1:, :].sum(0) + else: + E_ky_temp = 2*energy_fft.sum(0) + nkyE = self.nkyE + E_ky = E_ky_temp[0:nkyE] + E_ky[1:nkyE] = E_ky[1:nkyE] + E_ky_temp[self.nky_seq:nkyE:-1] + E_ky = E_ky/self.deltaky + E_ky = self.comm.allreduce(E_ky, op=MPI.SUM) + + elif not self.TRANSPOSED: + # In this case, self.dim_ky==0 and self.dim_ky==1 + # Memory is shared along ky + # note that only the kx>=0 are in the spectral variables + # to obtain the spectrum as a function of kx + # we sum over all ky + # the 2 is here because there are only the kx>=0 + E_kx = 2.*energy_fft.sum(self.dim_ky)/self.deltakx + E_kx[0] = E_kx[0]/2 + E_kx = self.comm.allreduce(E_kx, op=MPI.SUM) + E_kx = E_kx[:self.nkxE] + # computation of E_ky + E_ky_temp = energy_fft[:, 0].copy() + E_ky_temp += 2*energy_fft[:, 1:].sum(1) + E_ky_temp = np.ascontiguousarray(E_ky_temp) +# print self.rank, 'E_ky_temp', E_ky_temp, E_ky_temp.shape + E_ky_long = np.empty(self.nky_seq) + counts = self.comm.allgather(self.nky_loc) + self.comm.Allgatherv(sendbuf=[E_ky_temp, MPI.DOUBLE], + recvbuf=[E_ky_long, (counts, None), + MPI.DOUBLE]) + nkyE = self.nkyE + E_ky = E_ky_long[0:nkyE] + E_ky[1:nkyE] = E_ky[1:nkyE] + E_ky_long[self.nky_seq:nkyE:-1] + E_ky = E_ky/self.deltaky + +#### self.comm.barrier() +#### sleep(0.1) +#### print self.rank, 'E_kx.sum() =', E_kx.sum()*self.deltakx, \ +#### 'E_ky.sum() =', E_ky.sum()*self.deltaky,\ +#### 'diff = ', E_kx.sum()*self.deltakx-E_ky.sum()*self.deltaky + return E_kx, E_ky + + @cython.boundscheck(False) + @cython.wraparound(False) + def spectrum2D_from_fft(self, + np.ndarray[DTYPEf_t, ndim=2] E_fft): + """Compute the 2D spectra. Return a dictionary.""" + cdef np.ndarray[DTYPEf_t, ndim=2] KK + cdef np.uint32_t ikO, ik1, ikh, nkh + cdef np.uint32_t nk0loc, nk1loc, rank, TRANSPOSED + cdef DTYPEf_t E0D, kappa0D, deltakh, coef_share, energy + cdef np.ndarray[DTYPEf_t, ndim=1] spectrum2D, khE + + KK = self.KK + + nk0loc = self.shapeK_loc[0] + nk1loc = self.shapeK_loc[1] + + rank = self.rank + + if self.TRANSPOSED: + TRANSPOSED = 1 + else: + TRANSPOSED = 0 + + deltakh = self.deltakh + + khE = self.khE + nkh = self.nkhE + + spectrum2D = np.zeros([nkh]) + for ik0 in xrange(nk0loc): + for ik1 in xrange(nk1loc): + E0D = E_fft[ik0, ik1]/deltakh + kappa0D = KK[ik0, ik1] + + if TRANSPOSED == 0: + if ik1 > 0: + E0D = E0D*2 + else: + if ik0 > 0 or rank > 0: + E0D = E0D*2 + + ikh = int(kappa0D/deltakh) + + if ikh >= nkh-1: + ikh = nkh - 1 + spectrum2D[ikh] += E0D + else: + coef_share = (kappa0D - khE[ikh])/deltakh + spectrum2D[ikh] += (1-coef_share)*E0D + spectrum2D[ikh+1] += coef_share*E0D + + if nb_proc > 1: + spectrum2D = comm.allreduce(spectrum2D, op=MPI.SUM) + return spectrum2D + + def pdf_normalized(self, field, nb_bins=100): + """Compute the normalized pdf""" + + field_max = field.max() + field_min = field.min() + # field_mean = field.mean() + + if nb_proc > 1: + field_max = comm.allreduce(field_max, op=MPI.MAX) + field_min = comm.allreduce(field_min, op=MPI.MIN) + # field_mean = comm.allreduce(field_min, op=MPI.SUM)/nb_proc + + # rms = np.sqrt(np.mean( (field-field_mean)**2 )) + # range_min = field_mean - 20*rms + # range_max = field_mean + 20*rms + + # range_min = max(field_min, range_min) + # range_max = min(field_max, range_max) + + range_min = field_min + range_max = field_max + + if nb_proc == 1: + pdf, bin_edges = np.histogram(field, bins=nb_bins, + normed=True, + range=(range_min, range_max)) + else: + hist, bin_edges = np.histogram(field, bins=nb_bins, + range=(range_min, range_max)) + hist = comm.allreduce(hist, op=MPI.SUM) + pdf = hist/((bin_edges[1]-bin_edges[0])*hist.sum()) + return pdf, bin_edges + + def compute_increments_dim1(self, + np.ndarray[DTYPEf_t, ndim=2] var, + np.uint32_t irx): + """Compute the increments of var over the dim 1.""" + cdef np.uint32_t iO, i1, n0, n1, n1new + cdef np.ndarray[DTYPEf_t, ndim=2] inc_var + n0 = var.shape[0] + n1 = var.shape[1] + n1new = n1 - irx + inc_var = np.empty([n0, n1new]) + for i0 in xrange(n0): + for i1 in xrange(n1new): + inc_var[i0, i1] = (var[i0, i1+irx] - var[i0, i1]) + return inc_var + +#### functions for initialisation of field + def constant_arrayK(self, value=None, dtype=complex, SHAPE='LOC'): + """Return a constant array in spectral space.""" + if SHAPE == 'LOC': + shapeK = self.shapeK_loc + elif SHAPE == 'SEQ': + shapeK = self.shapeK_seq + elif SHAPE == 'GAT': + shapeK = self.shapeK_gat + else: + raise ValueError('SHAPE should be "LOC" or "SEQ"') + if value is None: + field_lm = np.empty(self.shapeK, dtype=dtype) + elif value == 0: + field_lm = np.zeros(self.shapeK, dtype=dtype) + else: + field_lm = value*np.ones(self.shapeK, dtype=dtype) + return field_lm + + def constant_arrayX(self, value=None, dtype=DTYPEf, SHAPE='LOC'): + """Return a constant array in real space.""" + if SHAPE == 'LOC': + shapeX = self.shapeX_loc + elif SHAPE == 'SEQ': + shapeX = self.shapeX_seq + else: + raise ValueError('SHAPE should be "LOC" of "SEQ"') + if value is None: + field = np.empty(shapeX, dtype=dtype) + elif value == 0: + field = np.zeros(shapeX, dtype=dtype) + else: + field = value*np.ones(shapeX, dtype=dtype) + return field + + def random_arrayK(self, SHAPE='LOC'): + """Return a random array in spectral space.""" + if SHAPE == 'LOC': + shapeK = self.shapeK_loc + elif SHAPE == 'SEQ': + shapeK = self.shapeK_seq + elif SHAPE == 'GAT': + shapeK = self.shapeK_gat + else: + raise ValueError('SHAPE should be "LOC", "GAT" or "SEQ"') + a_fft = (np.random.random(shapeK) + + 1j*np.random.random(shapeK) + - 0.5 - 0.5j) + return a_fft + + def random_arrayX(self, SHAPE='LOC'): + """Return a random array in real space.""" + if SHAPE == 'LOC': + shapeX = self.shapeX_loc + elif SHAPE == 'SEQ': + shapeX = self.shapeX_seq + else: + raise ValueError('SHAPE should be "LOC" or "SEQ"') + return np.random.random(shapeX) + + def project_fft_on_realX_seq( + self, np.ndarray[DTYPEc_t, ndim=2] f_fft): + """Project the given field in spectral space such as its + inverse fft is a real field.""" + cdef np.uint32_t nky_seq + cdef np.uint32_t iky_ky0, iky_kyM, ikx_kx0, ikx_kxM, + cdef np.uint32_t ikyp, ikyn + cdef DTYPEc_t f_kp_kx0, f_kn_kx0, f_kp_kxM, f_knp_kxM + + nky_seq = self.shapeK_seq[0] + + iky_ky0 = 0 + iky_kyM = nky_seq/2 + ikx_kx0 = 0 + # ikx_kxM = self.nkx_seq-1 + ikx_kxM = self.shapeK_seq[1]-1 + + # first, some values have to be real + f_fft[iky_ky0, ikx_kx0] = f_fft[iky_ky0, ikx_kx0].real + f_fft[iky_ky0, ikx_kxM] = f_fft[iky_ky0, ikx_kxM].real + f_fft[iky_kyM, ikx_kx0] = f_fft[iky_kyM, ikx_kx0].real + f_fft[iky_kyM, ikx_kxM] = f_fft[iky_kyM, ikx_kxM].real + + # second, there are relations between some values + for ikyp in xrange(1, iky_kyM): + ikyn = nky_seq - ikyp + + f_kp_kx0 = f_fft[ikyp, ikx_kx0] + f_kn_kx0 = f_fft[ikyn, ikx_kx0] + + f_fft[ikyp, ikx_kx0] = (f_kp_kx0+f_kn_kx0.conjugate() + )/2 + f_fft[ikyn, ikx_kx0] = ((f_kp_kx0+f_kn_kx0.conjugate() + )/2).conjugate() + + f_kp_kxM = f_fft[ikyp, ikx_kxM] + f_kn_kxM = f_fft[ikyn, ikx_kxM] + + f_fft[ikyp, ikx_kxM] = (f_kp_kxM+f_kn_kxM.conjugate() + )/2 + f_fft[ikyn, ikx_kxM] = ((f_kp_kxM+f_kn_kxM.conjugate() + )/2).conjugate() + + def coarse_seq_from_fft_loc(self, f_fft, shapeK_loc_coarse): + """Return a coarse field in K space.""" + nKyc = shapeK_loc_coarse[0] + nKxc = shapeK_loc_coarse[1] + + if nb_proc > 1: + fc_trans = np.empty([nKxc, nKyc], np.complex128) + nKy = self.shapeK_seq[0] + f1D_temp = np.empty([nKyc], np.complex128) + + for iKxc in xrange(nKxc): + kx = self.deltakx*iKxc + rank_iKx, iKxloc, iKyloc = self.where_is_wavenumber(kx, 0.) + if rank == rank_iKx: + # create f1D_temp + for iKyc in xrange(nKyc): + if iKyc <= nKyc/2: + iKy = iKyc + else: + kynodim = iKyc - nKyc + iKy = kynodim + nKy + f1D_temp[iKyc] = f_fft[iKxloc, iKy] + + if rank_iKx != 0: + # message f1D_temp + if rank == 0: + # print 'f1D_temp', f1D_temp, f1D_temp.dtype + comm.Recv( + [f1D_temp, MPI.DOUBLE_COMPLEX], + source=rank_iKx, tag=iKxc) + elif rank == rank_iKx: + comm.Send( + [f1D_temp, MPI.DOUBLE_COMPLEX], + dest=0, tag=iKxc) + if rank == 0: + # copy into fc_trans + fc_trans[iKxc] = f1D_temp.copy() + fc_fft = fc_trans.transpose() + + else: + nKy = self.shapeK_seq[0] + nKx = self.shapeK_seq[1] + fc_fft = np.empty([nKyc, nKxc], np.complex128) + for iKyc in xrange(nKyc): + if iKyc <= nKyc/2: + iKy = iKyc + else: + kynodim = iKyc - nKyc + iKy = kynodim + nKy + for iKxc in xrange(nKxc): + fc_fft[iKyc, iKxc] = f_fft[iKy, iKxc] + return fc_fft + + def fft_loc_from_coarse_seq(self, fc_fft, shapeK_loc_coarse): + """Return a large field in K space.""" + nKyc = shapeK_loc_coarse[0] + nKxc = shapeK_loc_coarse[1] + + if nb_proc > 1: + nKy = self.shapeK_seq[0] + f_fft = self.constant_arrayK(value=0.) + fc_trans = fc_fft.transpose() + + for iKxc in xrange(nKxc): + kx = self.deltakx*iKxc + rank_iKx, iKxloc, iKyloc = self.where_is_wavenumber(kx, 0.) + fc1D = fc_trans[iKxc] + if rank_iKx != 0: + # message fc1D + fc1D = np.ascontiguousarray(fc1D) + if rank == 0: + comm.Send(fc1D, dest=rank_iKx, tag=iKxc) + elif rank == rank_iKx: + comm.Recv(fc1D, source=0, tag=iKxc) + if rank == rank_iKx: + # copy + for iKyc in xrange(nKyc): + if iKyc <= nKyc/2: + iKy = iKyc + else: + kynodim = iKyc - nKyc + iKy = kynodim + nKy + f_fft[iKxloc, iKy] = fc1D[iKyc] + + else: + nKy = self.shapeK_seq[0] + nKx = self.shapeK_seq[1] + f_fft = np.zeros([nKy, nKx], np.complex128) + for iKyc in xrange(nKyc): + if iKyc <= nKyc/2: + iKy = iKyc + else: + kynodim = iKyc - nKyc + iKy = kynodim + nKy + for iKxc in xrange(nKxc): + f_fft[iKy, iKxc] = fc_fft[iKyc, iKxc] + return f_fft + + def monge_ampere_from_fft( + self, DTYPEc_t[:, :] a_fft, DTYPEc_t[:, :] b_fft): + cdef Py_ssize_t i0, n0, i1, n1 + cdef DTYPEc_t[:, :] pxx_afft, pyy_afft, pxy_afft + cdef DTYPEc_t[:, :] pxx_bfft, pyy_bfft, pxy_bfft + cdef DTYPEf_t[:, :] mamp + cdef DTYPEf_t[:, :] KX, KY, pxx_a, pyy_a, pxy_a, pxx_b, pyy_b, pxy_b + + n0 = a_fft.shape[0] + n1 = a_fft.shape[1] + KX = self.KX + KY = self.KY + + pxx_afft = np.empty([n0, n1], dtype=DTYPEc) + pyy_afft = np.empty([n0, n1], dtype=DTYPEc) + pxy_afft = np.empty([n0, n1], dtype=DTYPEc) + pxx_bfft = np.empty([n0, n1], dtype=DTYPEc) + pyy_bfft = np.empty([n0, n1], dtype=DTYPEc) + pxy_bfft = np.empty([n0, n1], dtype=DTYPEc) + + for i0 in xrange(n0): + for i1 in xrange(n1): + pxx_afft[i0, i1] = - a_fft[i0, i1] * KX[i0, i1]**2 + pyy_afft[i0, i1] = - a_fft[i0, i1] * KY[i0, i1]**2 + pxy_afft[i0, i1] = - a_fft[i0, i1] * KX[i0, i1]*KY[i0, i1] + pxx_bfft[i0, i1] = - b_fft[i0, i1] * KX[i0, i1]**2 + pyy_bfft[i0, i1] = - b_fft[i0, i1] * KY[i0, i1]**2 + pxy_bfft[i0, i1] = - b_fft[i0, i1] * KX[i0, i1]*KY[i0, i1] + pxx_a = self.ifft2(pxx_afft) + pyy_a = self.ifft2(pyy_afft) + pxy_a = self.ifft2(pxy_afft) + pxx_b = self.ifft2(pxx_bfft) + pyy_b = self.ifft2(pyy_bfft) + pxy_b = self.ifft2(pxy_bfft) + + mamp = np.empty_like(pxx_a) + n0 = mamp.shape[0] + n1 = mamp.shape[1] + for i0 in xrange(n0): + for i1 in xrange(n1): + mamp[i0, i1] = (pxx_a[i0, i1] * pyy_b[i0, i1] + + pyy_a[i0, i1] * pxx_b[i0, i1] - + 2 * pxy_a[i0, i1] * pxy_b[i0, i1]) + return np.array(mamp) + + def monge_ampere_from_fft_python(self, a_fft, b_fft): + KX = self.KX + KY = self.KY + ifft2 = self.ifft2 + + pxx_a = - ifft2(a_fft * KX**2) + pyy_a = - ifft2(a_fft * KY**2) + pxy_a = - ifft2(a_fft * KX * KY) + + pxx_b = - ifft2(b_fft * KX**2) + pyy_b = - ifft2(b_fft * KY**2) + pxy_b = - ifft2(b_fft * KX * KY) + + return pxx_a*pyy_b + pyy_a*pxx_b - 2*pxy_a*pxy_b + + def laplacian2_fft(self, DTYPEc_t[:, :] a_fft): + cdef Py_ssize_t i0, n0, i1, n1 + cdef DTYPEc_t[:, :] lap2_afft = np.empty_like(a_fft) + cdef DTYPEf_t[:, :] K4 = self.K4 + + n0 = a_fft.shape[0] + n1 = a_fft.shape[1] + for i0 in xrange(n0): + for i1 in xrange(n1): + lap2_afft[i0, i1] = a_fft[i0, i1] * K4[i0, i1] + return np.array(lap2_afft) + + def invlaplacian2_fft(self, DTYPEc_t[:, :] a_fft): + cdef Py_ssize_t i0, n0, i1, n1 + cdef DTYPEc_t[:, :] invlap2_afft = np.empty_like(a_fft) + cdef DTYPEf_t[:, :] K4_not0 = self.K4_not0 + + n0 = a_fft.shape[0] + n1 = a_fft.shape[1] + + for i0 in xrange(n0): + for i1 in xrange(n1): + invlap2_afft[i0, i1] = a_fft[i0, i1] / K4_not0[i0, i1] + + if rank == 0: + invlap2_afft[0, 0] = 0. + return np.array(invlap2_afft) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef np.ndarray loopKdealiasing(np.ndarray[DTYPEc_t, ndim=2] ff_fft, + np.ndarray[DTYPEb_t, ndim=2] where_dealiased, + int nK0loc, int nK1loc): + cdef np.uint32_t iKO, iK1 + for iK0 in range(nK0loc): + for iK1 in range(nK1loc): + if where_dealiased[iK0, iK1]: + ff_fft[iK0, iK1] = 0. diff --git a/fluidsim/operators/CySources/setofvariables_cy.pyx b/fluidsim/operators/CySources/setofvariables_cy.pyx new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL0N5U291cmNlcy9zZXRvZnZhcmlhYmxlc19jeS5weXg= --- /dev/null +++ b/fluidsim/operators/CySources/setofvariables_cy.pyx @@ -0,0 +1,252 @@ +"""Variable container (:mod:`fluidsim.operators.setofvariables`) +====================================================================== + +This module is written in cython and provides: + +.. currentmodule:: fluidsim.operators.setofvariables + +Provides: + +.. autoclass:: SetOfVariables + :members: + :private-members: + +""" + +# DEF MPI4PY = 0 + +cimport numpy as np +import numpy as np +np.import_array() + +try: + from mpi4py import MPI +except ImportError: + nb_proc = 1 + rank = 0 +else: + comm = MPI.COMM_WORLD + nb_proc = comm.size + rank = comm.Get_rank() + +IF MPI4PY: + from mpi4py cimport MPI + from mpi4py.mpi_c cimport * + + # solve an incompatibility between openmpi and mpi4py versions + cdef extern from 'mpi-compat.h': pass + + +from time import time, sleep +import datetime +import os +import matplotlib.pyplot as plt +import cython + +from libc.math cimport exp + + +# we define python and c types for physical and Fourier spaces +DTYPEb = np.uint8 +ctypedef np.uint8_t DTYPEb_t +DTYPEi = np.int +ctypedef np.int_t DTYPEi_t +DTYPEf = np.float64 +ctypedef np.float64_t DTYPEf_t +DTYPEc = np.complex128 +ctypedef np.complex128_t DTYPEc_t + +# Basically, you use the _t ones when you need to declare a type +# (e.g. cdef foo_t var, or np.ndarray[foo_t, ndim=...]. Ideally someday +# we won't have to make this distinction, but currently one is a C type +# and the other is a python object representing a numpy type (a dtype), +# and there's currently no way to identify the two without special +# compiler support. +# - Robert Bradshaw + + +class SetOfVariables(object): + """Gather a set of variables in a Numpy array. + + + """ + __array_priority__ = 100 + + @property + def nbytes(self): + return self.data.nbytes + + def __init__(self, + keys=None, shape1var=None, + dtype=None, name_type_variables=None, + like_this_sov=None, value=None): + if like_this_sov is not None: + keys = like_this_sov.keys + self.nb_variables = like_this_sov.data.shape[0] + shape1var = like_this_sov.data.shape[1:] + if dtype is None: + dtype = like_this_sov.data.dtype + if name_type_variables is None: + name_type_variables = like_this_sov.name_type_variables + else: + if dtype is None: + dtype = np.float64 + keys.sort() + self.nb_variables = len(keys) + + self.name_type_variables = name_type_variables + self.keys = keys + shape = [self.nb_variables] + shape.extend(shape1var) + if value is None: + self.data = np.empty(shape, dtype=dtype) + elif value == 0: + self.data = np.zeros(shape, dtype=dtype) + else: + self.data = value*np.ones(shape, dtype=dtype) + + dimension_space = len(shape1var) + if dimension_space == 1: + self.dealiasing = self._dealiasing1d + elif dimension_space == 2: + self.dealiasing = self._dealiasing2d + elif dimension_space == 3: + self.dealiasing = self._dealiasing3d + else: + raise ValueError( + 'Space dimension {} not implemented in SetOfVariables.'.format( + dimension_space)) + + def __getitem__(self, key): + ik = self.keys.index(key) + return self.data[ik] + + def __setitem__(self, key, value): + ik = self.keys.index(key) + self.data[ik][:] = value + + def __add__(self, other): + if isinstance(other, self.__class__): + dtype_new = max_dtype(self.data, other.data) + obj_result = self.__class__(like_this_sov=other, dtype=dtype_new) + obj_result.data = self.data + other.data + elif isinstance(other, (int, float, complex)): + dtype_new = max_dtype(self.data, other) + obj_result = self.__class__(like_this_sov=self, dtype=dtype_new) + obj_result.data = self.data + other + return obj_result + __radd__ = __add__ + + def __iadd__(self, other): + if isinstance(other, self.__class__): + dtype_new = max_dtype(self.data, other.data) + if dtype_new != self.data.dtype: + obj_result = self.__class__( + like_this_sov=self, dtype=dtype_new) + else: + obj_result = self + obj_result.data += other.data + elif isinstance(other, (int, float, complex)): + dtype_new = max_dtype(self.data, other) + if dtype_new != self.data.dtype: + obj_result = self.__class__( + like_this_sov=self, dtype=dtype_new) + else: + obj_result = self + obj_result.data += other.data + return obj_result + + def __sub__(self, other): + if isinstance(other, self.__class__): + dtype_new = max_dtype(self.data, other.data) + obj_result = self.__class__(like_this_sov=other, dtype=dtype_new) + obj_result.data = self.data - other.data + elif isinstance(other, (int, float, complex)): + dtype_new = max_dtype(self.data, other) + obj_result = self.__class__(like_this_sov=self, dtype=dtype_new) + obj_result.data = self.data - other + return obj_result + + def __mul__(self, other): + if isinstance(other, (int, float, np.ndarray)): + dtype_new = max_dtype(self.data, other) + obj_result = self.__class__(like_this_sov=self, dtype=dtype_new) + obj_result.data = other*self.data + return obj_result + __rmul__ = __mul__ + + def __div__(self, other): + if isinstance(other, (int, float, np.ndarray)): + dtype_new = max_dtype(self.data, other) + obj_result = self.__class__(like_this_sov=self, dtype=dtype_new) + obj_result.data = self.data/other + return obj_result + + def initialize(self, value=0.): + self.data = value*np.ones(self.data.shape, + dtype=self.data.dtype) + + def _dealiasing1d(self, DTYPEb_t[:] where_dealiased): + cdef DTYPEc_t[:, :] data = self.data + cdef Py_ssize_t ik, nk, i0, n0 + + nk = self.nb_variables + n0 = data.shape[1] + + for i0 in xrange(n0): + if where_dealiased[i0]: + for ik in xrange(nk): + data[ik, i0] = 0. + + def _dealiasing2d(self, DTYPEb_t[:, :] where_dealiased): + cdef DTYPEc_t[:, :, :] data = self.data + cdef Py_ssize_t ik, nk, i0, n0, i1, n1 + + nk = self.nb_variables + n0 = data.shape[1] + n1 = data.shape[2] + + for i0 in xrange(n0): + for i1 in xrange(n1): + if where_dealiased[i0, i1]: + for ik in xrange(nk): + data[ik, i0, i1] = 0. + + def _dealiasing3d(self, DTYPEb_t[:, :, :] where_dealiased): + cdef DTYPEc_t[:, :, :, :] data = self.data + cdef Py_ssize_t ik, nk, i0, n0, i1, n1, i2, n2 + + nk = self.nb_variables + n0 = data.shape[1] + n1 = data.shape[2] + n2 = data.shape[3] + + for i0 in xrange(n0): + for i1 in xrange(n1): + for i2 in xrange(n2): + if where_dealiased[i0, i1, i2]: + for ik in xrange(nk): + data[ik, i0, i1, i2] = 0. + + +def max_dtype(A, B): + '''Return the dtype of the result of an operation involving A and B.''' + # it would be better to just use + try: + # this function is only available in numpy 1.6 + return np.result_type(A, B) + except AttributeError: + if isinstance(A, np.ndarray): + dtypeA = A.dtype + else: + dtypeA = np.array(A).dtype + + if isinstance(B, np.ndarray): + dtypeB = B.dtype + else: + dtypeB = np.array(B).dtype + + if dtypeA <= dtypeB: + return dtypeB + else: + return dtypeA diff --git a/fluidsim/operators/__init__.py b/fluidsim/operators/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL19faW5pdF9fLnB5 --- /dev/null +++ b/fluidsim/operators/__init__.py @@ -0,0 +1,27 @@ +"""Numerical operators (:mod:`fluidsim.operators`) +======================================================== + +.. currentmodule:: fluidsim.operators + +Provides + +.. autosummary:: + :toctree: + + operators + setofvariables + fft + +.. todo:: Make a nice hierarchy of Operators classes. + + - Operators + - OperatorsPseudoSpectral1D + - OperatorsPseudoSpectral2D + - OperatorsPseudoSpectral3D + - OperatorsFiniteDiff1D + - OperatorsFiniteDiff2D + - OperatorsFiniteDiff3D + - OperatorsPseudoSpectral1DFiniteDiff1D + - OperatorsPseudoSpectral2DFiniteDiff1D + +""" diff --git a/fluidsim/operators/fft/Sources_fftw2dccy/fft2Dsolveq2D.c b/fluidsim/operators/fft/Sources_fftw2dccy/fft2Dsolveq2D.c new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC9Tb3VyY2VzX2ZmdHcyZGNjeS9mZnQyRHNvbHZlcTJELmM= --- /dev/null +++ b/fluidsim/operators/fft/Sources_fftw2dccy/fft2Dsolveq2D.c @@ -0,0 +1,443 @@ +/* test_fftw3_2Dmpi_simple program + +export USR_PERSO='/home/pierre/usr' +export USR_PERSO='/scratch/augier/usr' + +Compiled with: +gcc -O3 fft2Dsolveq2D.c -I$USR_PERSO/include/ \ +$USR_PERSO/lib/libfftw3.so -lm -o fft2Dsolveq2D + +Create the library: +gcc -c -fPIC fft2Dsolveq2D.c -I$USR_PERSO/include -o fft2Dsolveq2D.o +gcc fft2Dsolveq2D.o -shared -o libfft2Dsolveq2D.so $USR_PERSO/lib/libfftw3.so -lm +mv libfft2Dsolveq2D.so $USR_PERSO/lib + + + +And on ferlin: +gcc -O3 fft2Dsolveq2D.c -I${FFTW_HOME}/double/include \ +-L${FFTW_HOME}/double/lib -lfftw3 -lm -o fft2Dsolveq2D + +Create the library: +gcc -c -fPIC fft2Dsolveq2D.c -I${FFTW_HOME}/double/include -o fft2Dsolveq2D.o + +gcc -shared -o libfft2Dsolveq2D.so fft2Dsolveq2D.o \ +${FFTW_HOME}/double/lib/libfftw3.so -lm + + +mpicc -c -fPIC fft2Dmpisolveq2D.c -I${FFTW_HOME}/double/include \ +-o fft2Dmpisolveq2D.o +mpicc -shared -o libfft2Dmpisolveq2D.so fft2Dmpisolveq2D.o \ +-L${FFTW_HOME}/double/lib -lm -lfftw3 + + + + +execute with +mpirun -np 2 ./fft2Dsolveq2D +(the 2 after "-np" is the number of processors) +*/ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <complex.h> +#include <fftw3.h> +#include <sys/time.h> + + +#include <unistd.h> + +#include <math.h> + +#include "fft2Dsolveq2D.h" + + + + +Util_fft init_Util_fft(int N0, int N1) + { + Util_fft uf; + struct timeval start_time, end_time; + double total_usecs; + int ii, jj, irank, iX0; + int istride = 1, ostride = 1; + int howmany, sign; + + +/* printf("init_util_fft, N0 = %d, N1 = %d\n", N0, N1);*/ + + uf.N0 = N0; + uf.N1 = N1; + + /* y corresponds to dim 0 in physical space */ + /* x corresponds to dim 1 in physical space */ + uf.ny = N0; + uf.nx = N1; + + uf.nX0 = N0; + uf.nX1 = N1; + uf.nX0loc = N0; + uf.nXyloc = uf.nX0loc; + + uf.nKx = uf.nx/2; + uf.nKxloc = uf.nKx; + uf.nKy = uf.ny; + + /* This 2D fft is transposed */ + uf.nK0 = N1/2; + uf.nK0loc = uf.nK0; + uf.nK1 = N0; + + uf.coef_norm = N0*N1; + + uf.flags = FFTW_MEASURE; +/* flags = FFTW_ESTIMATE;*/ +/* uf.flags = FFTW_PATIENT;*/ + + uf.arrayX = (double*) fftw_malloc(sizeof(double)*uf.nX0loc*N1); + uf.arrayK_pR = (fftw_complex*) fftw_malloc( sizeof(fftw_complex) + *uf.nX0loc*(uf.nKx+1)); + uf.arrayK_pC = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*uf.nKxloc*N0); + + +/* if ((uf.rank)==0) printf("create plans\n");*/ + gettimeofday(&start_time, NULL); +/* plan = fftw_plan_many_dft(int rank, const int *n, int howmany,*/ +/* fftw_complex *in, const int *inembed,*/ +/* int istride, int idist,*/ +/* fftw_complex *out, const int *onembed,*/ +/* int ostride, int odist,*/ +/* int sign, unsigned flags);*/ + + howmany = uf.nX0loc; + uf.plan_r2c = fftw_plan_many_dft_r2c( 1, &N1, howmany, + uf.arrayX, NULL, + istride, N1, + uf.arrayK_pR, NULL, + ostride, uf.nKx+1, + uf.flags); + + uf.plan_c2r = fftw_plan_many_dft_c2r( 1, &N1, howmany, + uf.arrayK_pR, NULL, + istride, uf.nKx+1, + uf.arrayX, NULL, + ostride, N1, + uf.flags); + + howmany = uf.nKxloc; + sign = FFTW_FORWARD; + uf.plan_c2c_fwd = fftw_plan_many_dft( 1, &N0, howmany, + uf.arrayK_pC, &N0, + istride, N0, + uf.arrayK_pC, &N0, + ostride, N0, + sign, uf.flags); + sign = FFTW_BACKWARD; + uf.plan_c2c_bwd = fftw_plan_many_dft( 1, &N0, howmany, + uf.arrayK_pC, &N0, + istride, N0, + uf.arrayK_pC, &N0, + ostride, N0, + sign, uf.flags); + + gettimeofday(&end_time, NULL); + total_usecs = (end_time.tv_sec-start_time.tv_sec) + + (end_time.tv_usec-start_time.tv_usec)/1000000.; +/* printf (" done in %f s\n", */ +/* total_usecs);*/ + + for (iX0=0;iX0<uf.nX0loc;iX0++) + uf.arrayK_pR[iX0*(uf.nKx+1)+uf.nKx] = 0.; + + + +/* for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nKx+1; ++jj)*/ +/* {*/ +/* printf("%i , uf.arrayK_pR[%i*(uf.nKx+1) + %i] = (%6.4f, %6.4f)\n", */ +/* uf.rank, ii, jj,*/ +/* creal(uf.arrayK_pR[ii*(uf.nKx+1) + jj]), */ +/* cimag(uf.arrayK_pR[ii*(uf.nKx+1) + jj]));*/ +/* }*/ + + + + + return uf; +} + + + +void destroy_Util_fft(Util_fft uf) + { +/* if ((uf.rank)==0) printf("destroy_util_fft\n");*/ + fftw_destroy_plan(uf.plan_r2c); + fftw_destroy_plan(uf.plan_c2c_fwd); + fftw_destroy_plan(uf.plan_c2c_bwd); + fftw_destroy_plan(uf.plan_c2r); + fftw_free(uf.arrayX); + fftw_free(uf.arrayK_pR); + fftw_free(uf.arrayK_pC); +} + + + + + + + + + +void fft2D(Util_fft uf, double *fieldX, fftw_complex *fieldK) + { + int ii, jj; + /*use memcpy(void * destination, void * source, size_t bytes); */ + + memcpy(uf.arrayX, fieldX, uf.nX0loc*uf.nX1*sizeof(double)); + + +/* for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.N1; ++jj)*/ +/* {*/ +/* printf( "%d , uf.arrayX[%d,%d] = %6.4f\n", */ +/* uf.rank, ii, jj, uf.arrayX[ii*uf.N1+jj]);*/ +/* }*/ + + + fftw_execute(uf.plan_r2c); + +/* printf("print uf.arrayK_pR after alltoall\n");*/ + +/* for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nKx+1; ++jj)*/ +/* {*/ +/* printf("%i , uf.arrayK_pR[%i*(uf.nKx+1) + %i] = (%6.4f, %6.4f)\n", */ +/* uf.rank, ii, jj,*/ +/* creal(uf.arrayK_pR[ii*(uf.nKx+1)+jj]), */ +/* cimag(uf.arrayK_pR[ii*(uf.nKx+1)+jj]));*/ +/* }*/ + +/* second step: transpose...*/ + for (ii = 0; ii < uf.nX0; ++ii) for (jj = 0; jj < uf.nKx; ++jj) + uf.arrayK_pC[jj*uf.nX0+ii] = + uf.arrayK_pR[ii*(uf.nKx+1)+jj]; + + +/* for (ii = 0; ii < uf.nKxloc; ++ii) for (jj = 0; jj < uf.N0; ++jj)*/ +/* {*/ +/* printf("%i , uf.arrayK_pC[%i*uf.N0 + %i] = (%6.4f, %6.4f)\n", */ +/* uf.rank, ii, jj,*/ +/* creal(uf.arrayK_pC[ii*uf.N0 + jj]), */ +/* cimag(uf.arrayK_pC[ii*uf.N0 + jj]));*/ +/* }*/ + + + + fftw_execute(uf.plan_c2c_fwd); + + +/* for (ii = 0; ii < uf.nKxloc; ++ii) for (jj = 0; jj < uf.N0; ++jj)*/ +/* {*/ +/* printf("%i , uf.arrayK_pC[%i*uf.N0 + %i] = (%6.4f, %6.4f)\n", */ +/* uf.rank, ii, jj,*/ +/* creal(uf.arrayK_pC[ii*uf.N0 + jj]), */ +/* cimag(uf.arrayK_pC[ii*uf.N0 + jj]));*/ +/* }*/ + + + + for (ii=0; ii<uf.nKxloc*uf.nKy; ii++) + fieldK[ii] = uf.arrayK_pC[ii]/uf.coef_norm; + + } + + + + +void ifft2D(Util_fft uf, fftw_complex *fieldK, double *fieldX) + { + int ii, jj, irank; + /*use memcpy(void * destination, void * source, size_t bytes); */ + memcpy(uf.arrayK_pC, fieldK, uf.nKxloc*uf.nKy*sizeof(fftw_complex)); + fftw_execute(uf.plan_c2c_bwd); + + /* second step: transpose...*/ + for (ii = 0; ii < uf.nKx; ++ii) for (jj = 0; jj < uf.nX0; ++jj) + uf.arrayK_pR[jj*(uf.nKx+1)+ii] = + uf.arrayK_pC[ii*uf.nX0+jj]; + + /* these modes (nx/2+1=N1/2+1) have to be settled to zero*/ + for (ii = 0; ii < uf.nX0loc; ++ii) + uf.arrayK_pR[ii*(uf.nKx+1) + uf.nKx] = 0.; + + fftw_execute(uf.plan_c2r); + memcpy(fieldX,uf.arrayX, uf.nX0loc*uf.nX1*sizeof(double)); + } + + +void time_execute(Util_fft uf, double *fieldX, + fftw_complex *fieldK, int nb_time_execute) + { + int ii; + struct timeval start_time, end_time; + double total_usecs; + + if (uf.rank==0) printf("timer...\n"); + gettimeofday(&start_time, NULL); + for (ii=0; ii<nb_time_execute; ii++) + { + fft2D(uf, fieldX, fieldK); + ifft2D(uf, fieldK, fieldX); + } + gettimeofday(&end_time, NULL); + + + total_usecs = (end_time.tv_sec-start_time.tv_sec) + + (end_time.tv_usec-start_time.tv_usec)/1000000.; + printf ("%d times forward and backward sequencial FFT: %f s\n", + nb_time_execute ,total_usecs); +} + + + +int main(int argc, char **argv) + { + const int N0 = 4, N1 = 4; + Util_fft uf; + double * fieldX, * fieldX_0; + fftw_complex * fieldK, * fieldK_0; + int ii, jj, irank; + double energyK, energyX, energy2; + int coef; + + + uf = init_Util_fft(N0,N1); + + srand(time(NULL)+uf.rank*uf.nb_proc); + + printf( "I'm rank (processor number) %i of size %i\n", + uf.rank, uf.nb_proc); + + fieldX = (double *) malloc(uf.nX0loc*uf.nX1 * sizeof(double)); + fieldX_0 = (double *) malloc(uf.nX0loc*uf.nX1 * sizeof(double)); + fieldK = (fftw_complex *) malloc(uf.nKxloc*uf.nKy * sizeof(fftw_complex)); + fieldK_0 = (fftw_complex *) malloc(uf.nKxloc*uf.nKy * sizeof(fftw_complex)); + + +/* time_execute(uf, fieldX, fieldK, 100);*/ + + + + for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nX1; ++jj) + { +/* fieldX[ii*uf.nX1+jj] = 2.;*/ + fieldX[ii*uf.nX1+jj] = rand()/(double)RAND_MAX -0.5; + } + + for (ii = 0; ii < uf.nKxloc; ++ii) for (jj = 0; jj < uf.nKy; ++jj) + { + fieldK[ii*uf.nKy+jj] = 0.; + } + +/* We have to project on the space available for this library */ + fft2D(uf, fieldX, fieldK); + ifft2D(uf, fieldK, fieldX); + + + + for (ii = 0; ii < uf.nKxloc; ++ii) for (jj = 0; jj < uf.nKy; ++jj) + fieldK_0[ii*uf.nKy+jj] = fieldK[ii*uf.nKy+jj]; + + for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nX1; ++jj) + fieldX_0[ii*uf.nX1+jj] = fieldX[ii*uf.nX1+jj]; + + + + /* if (uf.rank==0) */ + /* { */ +/* fieldK[0*uf.nKy+0] = 1.;*/ + +/* fieldK[0*uf.nKy+1] = 1.I;*/ +/* fieldK[0*uf.nKy+3] = -1.I;*/ + + /* fieldK[0*uf.nKy+2] = 1.; */ + /* } */ + + +/* if (uf.rank==1) */ +/* {*/ +/* fieldK[0*uf.nKy+0] = 1.;*/ + +/* fieldK[0*uf.nKy+1] = 1.I;*/ +/* fieldK[0*uf.nKy+3] = -1.I;*/ + +/* fieldK[0*uf.nKy+2] = 1.I;*/ +/* }*/ + + + + + + + + + time_execute(uf, fieldX, fieldK, 100); + + + + for (ii = 0; ii < uf.nKxloc; ++ii) for (jj = 0; jj < uf.nKy; ++jj) + { + printf("%i , fieldK[%i*nKxloc + %i] = (%6.4f, %6.4f)\n", + uf.rank, ii, jj, + creal(fieldK[ii*uf.nKy + jj]), + cimag(fieldK[ii*uf.nKy + jj])); + } + + + for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nX1; ++jj) + { + printf( "%d , fieldX[%d,%d] = %+6.4f\n", + uf.rank, ii, jj, fieldX[ii*uf.nX1+jj]); + } + + for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nX1; ++jj) + { + printf("%d , (fieldX - fieldX_0)[%d,%d] = %+6.4f\n", + uf.rank, ii, jj, + fieldX[ii*uf.nX1+jj]-fieldX_0[ii*uf.nX1+jj]); + } + + energyX = 0.; + for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nX1; ++jj) + { + energyX += fieldX[ii*uf.nX1+jj]*fieldX[ii*uf.nX1+jj]; + } + energyX = energyX/uf.coef_norm; + + printf( "energyX = %6.4f\n", energyX); + + + energyK = 0.; + for (ii = 0; ii < uf.nKxloc; ++ii) for (jj = 0; jj < uf.nKy; ++jj) + { + if (ii==0) + coef = 1; + else + coef = 2; + energyK += pow(cabs(fieldK[ii*uf.nKy + jj]), 2) *coef; + } + + printf( "energyK = %6.4f\n", energyK); + + + + + + + free(fieldX); + free(fieldX_0); + free(fieldK); + + destroy_Util_fft(uf); + } + + + diff --git a/fluidsim/operators/fft/Sources_fftw2dccy/fft2Dsolveq2D.h b/fluidsim/operators/fft/Sources_fftw2dccy/fft2Dsolveq2D.h new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC9Tb3VyY2VzX2ZmdHcyZGNjeS9mZnQyRHNvbHZlcTJELmg= --- /dev/null +++ b/fluidsim/operators/fft/Sources_fftw2dccy/fft2Dsolveq2D.h @@ -0,0 +1,26 @@ + + +typedef struct +{ + /* X and K denote physical and Fourier spaces. */ + /* y corresponds to dim 0 in physical space */ + /* x corresponds to dim 1 in physical space */ + int N0, N1, nX0, nX1, nX0loc; + int ny, nx, nXyloc; + /* y corresponds to dim 1 in Fourier space */ + /* x corresponds to dim 0 in Fourier space */ + int nK0, nK1, nK0loc; + int nKx, nKy, nKxloc; + int coef_norm; + fftw_plan plan_r2c, plan_c2c_fwd, plan_c2r, plan_c2c_bwd; + double *arrayX; + fftw_complex *arrayK_pR, *arrayK_pC; + unsigned flags; + int rank, nb_proc, irank; +} Util_fft; + +Util_fft init_Util_fft(int N0, int N1); +void destroy_Util_fft(Util_fft uf); +void fft2D(Util_fft uf, double *fieldX, fftw_complex *fieldK); +void ifft2D(Util_fft uf, fftw_complex *fieldK, double *fieldX); + diff --git a/fluidsim/operators/fft/Sources_fftw2dccy/fft2Dsolveq2D.pxd b/fluidsim/operators/fft/Sources_fftw2dccy/fft2Dsolveq2D.pxd new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC9Tb3VyY2VzX2ZmdHcyZGNjeS9mZnQyRHNvbHZlcTJELnB4ZA== --- /dev/null +++ b/fluidsim/operators/fft/Sources_fftw2dccy/fft2Dsolveq2D.pxd @@ -0,0 +1,34 @@ + +cdef extern from "complex.h": + pass + +cdef extern from "fftw3.h": + ctypedef struct fftw_plan_s: + pass + ctypedef fftw_plan_s *fftw_plan + +cdef extern from "fft2Dmpisolveq2D.h": + ctypedef struct Util_fft: + # X and K denote physical and Fourier spaces + # y corresponds to dim 0 in physical space + # x corresponds to dim 1 in physical space + int N0, N1, nX0, nX1, nX0loc + int ny, nx, nXyloc + # y corresponds to dim 1 in Fourier space + # x corresponds to dim 0 in Fourier space + int nK0, nK1, nK0loc + int nKx, nKy, nKxloc + int coef_norm + fftw_plan plan_r2c, plan_c2c_fwd, plan_c2r, plan_c2c_bwd + double *arrayX + complex *arrayK_pR, *arrayK_pC + unsigned flags + + + + + Util_fft init_Util_fft(int N0, int N1) + void destroy_Util_fft(Util_fft uf) + void fft2D(Util_fft uf, double *fieldX, complex *fieldK) + void ifft2D(Util_fft uf, complex *fieldK, double *fieldX) + diff --git a/fluidsim/operators/fft/Sources_fftw2dmpiccy/fftw2dmpiccy.pyx b/fluidsim/operators/fft/Sources_fftw2dmpiccy/fftw2dmpiccy.pyx new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC9Tb3VyY2VzX2ZmdHcyZG1waWNjeS9mZnR3MmRtcGljY3kucHl4 --- /dev/null +++ b/fluidsim/operators/fft/Sources_fftw2dmpiccy/fftw2dmpiccy.pyx @@ -0,0 +1,308 @@ + + +cimport numpy as np +import numpy as np +np.import_array() + +from mpi4py import MPI +from mpi4py cimport MPI +from mpi4py.mpi_c cimport * + +# fix a bug arising when using a recent version of mpi4py +cdef extern from 'mpi-compat.h': pass + +from cpython cimport Py_INCREF + +cimport libc +from libc.stddef cimport ptrdiff_t + +from libc.stdlib cimport malloc, free + +# this refers to the .pxd file +cimport libcfftw2dmpi + +cdef extern from "numpy/arrayobject.h": + object PyArray_SimpleNewFromData(int nd, int* dims, + int typenum, void* data) + +# we define python and c types for physical and Fourier spaces +DTYPEb = np.uint8 +ctypedef np.uint8_t DTYPEb_t +DTYPEi = np.int +ctypedef np.int_t DTYPEi_t +DTYPEf = np.float64 +ctypedef np.float64_t DTYPEf_t +DTYPEc = np.complex128 +ctypedef np.complex128_t DTYPEc_t + +comm = MPI.COMM_WORLD + +cdef class FFT2Dmpi(object): + '''A FFT2Dmpi object is a wrapper to a c library +doing 2D parallele fft which uses MPI and sequencial functions +of the fftw library. +''' + # number of nodes in the first and second dimensions + cdef int N0, N1 + cdef public DTYPEb_t TRANSPOSED + + cdef libcfftw2dmpi.Util_fft uf + + # shape of the arrays in the physical and Fourier spaces, + # for the sequential case: + cdef public np.ndarray shapeX_seq, shapeK_seq, shapeK_gat + # and for the parallel case: + cdef public np.ndarray shapeX_loc, shapeK_loc + + # the communicator, nb of processus and rank of the processus + cdef MPI.Comm comm + cdef public int nb_proc, rank + + cdef public int iX0loc_start, iK1loc_start, iK0loc_start + cdef public int iKxloc_start, iKyloc_start + + cdef public int idimx, idimy, idimkx, idimky + + cdef DTYPEc_t *carrayK + cdef DTYPEf_t *carrayX + + def __init__(self, int N0, int N1): + + # info on MPI + self.comm = comm + self.nb_proc = self.comm.size + self.rank = self.comm.Get_rank() + + if N0%2 != 0 or N1%2 != 0: + raise ValueError('conditions n0, n1 have to be even') + + if N0%self.nb_proc != 0 or N1/2%self.nb_proc != 0: + raise ValueError( + 'fftw2dmpiccy works only' + ' if N0%self.nb_proc==0 and N1/2%self.nb_proc==0') + + self.N0 = N0 + self.N1 = N1 + + # for sequenciel runs (not implemented with this library) + # the data in K space is not transposed + self.shapeX_seq = np.array([N0, N1]) + self.shapeK_seq = np.array([N0, N1/2+1]) + + self.shapeK_gat = np.array([N1/2, N0]) + + # the figures 0 and 1 correspond to the dimension in physical space, + # the dimension 0 corresponds to the y-axes. + # and the dimension 1 corresponds to the x-axes. + self.idimx = 1 + self.idimy = 0 + self.idimkx = 0 + self.idimky = 1 + + self.TRANSPOSED = 1 + + # initialisation of the fft + self.uf = libcfftw2dmpi.init_Util_fft(N0, N1) + + self.shapeX_loc = np.array([self.uf.nX0loc, self.uf.nX1]) + self.shapeK_loc = np.array([self.uf.nKxloc, self.uf.nKy]) + + self.iX0loc_start = self.uf.nX0loc*self.rank + self.iK0loc_start = self.uf.nK0loc*self.rank + + self.iK1loc_start = 0 + + self.iKxloc_start = self.iK0loc_start + self.iKyloc_start = self.iK1loc_start + + # allocation de carray + self.carrayX = <DTYPEf_t *> malloc(self.shapeX_loc.prod() + * sizeof(DTYPEf_t)) + self.carrayK = <DTYPEc_t *> malloc(self.shapeK_loc.prod() + * sizeof(DTYPEc_t)) + + + # cpdef fft2d(self, np.ndarray[DTYPEf_t, ndim=2] ffX): + cpdef fft2d(self, DTYPEf_t[:, :] ffX): + cdef np.ndarray[DTYPEf_t, ndim=2, mode="c"] ffX_cont + cdef np.ndarray[DTYPEc_t, ndim=2, mode="c"] ffK_cont + ffX_cont = np.ascontiguousarray(ffX, dtype=DTYPEf) + ffK_cont = np.empty(self.shapeK_loc, dtype=DTYPEc) + libcfftw2dmpi.fft2D(self.uf, &ffX_cont[0,0], &ffK_cont[0,0]) + return ffK_cont + + # cpdef ifft2d(self, np.ndarray[DTYPEc_t, ndim=2] ffK): + cpdef ifft2d(self, DTYPEc_t[:, :] ffK): + cdef np.ndarray[DTYPEc_t, ndim=2, mode="c"] ffK_cont + cdef np.ndarray[DTYPEf_t, ndim=2, mode="c"] ffX_cont + ffK_cont = np.ascontiguousarray(ffK, dtype=DTYPEc) + ffX_cont = np.empty(self.shapeX_loc, dtype=DTYPEf) + libcfftw2dmpi.ifft2D(self.uf, &ffK_cont[0,0], &ffX_cont[0,0]) + return ffX_cont + + def __dealloc__(self): + libcfftw2dmpi.destroy_Util_fft(self.uf) + + def describe(self): + if self.rank == 0: + print 'object of class Myfft2Dmpi' + print 'N0 =', self.N0, 'N1 =', self.N1 + print 'nb_proc =', self.nb_proc, + if self.nb_proc == 1: + print '=> sequenciel version' + else: + print '=> parallel version (MPI)' + + def gather_Xspace(self, np.ndarray ff_loc, + root=None, type DTYPE=DTYPEf): + cdef np.ndarray ff_seq + + # self.shapeX_loc is the same for all processes, + # it is safe to use Allgather or Gather + if root is None: + ff_seq = np.empty(self.shapeX_seq, DTYPE) + self.comm.Allgather(ff_loc, ff_seq) + elif isinstance(root, int): + ff_seq = None + if self.rank == root: + ff_seq = np.empty(self.shapeX_seq, DTYPE) + self.comm.Gather(ff_loc, ff_seq, root=root) + else: + raise ValueError('root should be an int') + return ff_seq + + def scatter_Xspace(self, np.ndarray ff_seq, + int root=0, type DTYPE=DTYPEf): + cdef np.ndarray ff_loc + ff_loc = np.empty(self.shapeX_loc, dtype=DTYPE) + # self.shapeX_loc is the same for all processes, + # it is safe to use Scatter + if isinstance(root, int): + self.comm.Scatter(ff_seq, ff_loc, root=root) + else: + raise ValueError('root should be an int') + return ff_loc + + def gather_Kspace(self, np.ndarray ff_fft_loc, root=None, AS_SEQ=True): + cdef np.ndarray ff_fft_seq + cdef np.ndarray shapeK_temp + rowtype = MPI.COMPLEX16.Create_contiguous(self.shapeK_loc[1]) + rowtype.Commit() + if root is None: + ff_fft_gat = np.empty(self.shapeK_gat, dtype=DTYPEc) +# counts1 = self.comm.allgather(self.shapeK_loc[0]) +# print 'counts1 =', counts1 + counts = np.ones([self.nb_proc], dtype=int)*self.shapeK_loc[0] +# print 'counts =', counts + self.comm.Allgatherv(sendbuf=[ff_fft_loc, MPI.COMPLEX16], + recvbuf=[ff_fft_gat, (counts, None), rowtype]) + if AS_SEQ: + ff_fft_gat = ff_fft_gat.transpose() + ff_fft_seq = np.empty(self.shapeK_seq, dtype=DTYPEc) + ff_fft_seq[:, :-1] = ff_fft_gat + ff_fft_seq[:, -1] = 0. + result = ff_fft_seq + else: + result = ff_fft_gat + elif isinstance(root, int): + ff_fft_gat = None + if self.rank == root: + ff_fft_gat = np.empty(self.shapeK_gat, dtype=DTYPEc) + counts = np.ones([self.nb_proc], dtype=int)*self.shapeK_loc[0] + self.comm.Gatherv(sendbuf=[ff_fft_loc, MPI.COMPLEX16], + recvbuf=[ff_fft_gat, (counts, None), rowtype], + root=root) + if AS_SEQ: + if self.rank == root: + ff_fft_gat = ff_fft_gat.transpose() + ff_fft_seq = np.empty(self.shapeK_seq, dtype=DTYPEc) + ff_fft_seq[:, :-1] = ff_fft_gat + ff_fft_seq[:, -1] = 0. + result = ff_fft_seq + else: + result = None + else: + result = ff_fft_gat + else: + raise ValueError('root should be an int') + rowtype.Free() + return result + + def scatter_Kspace(self, np.ndarray ff_fft_seq, int root=0, + AS_SEQ=True, type DTYPE=DTYPEc): + cdef np.ndarray ff_fft_loc + cdef np.ndarray shapeK_temp + + if not isinstance(root, int): + raise ValueError('root should be an int') + if AS_SEQ and root == self.rank: + ff_fft_seq = ff_fft_seq[:, :-1] + ff_fft_seq = ff_fft_seq.transpose() + + ff_fft_loc = np.empty(self.shapeK_loc, dtype=DTYPE) + # self.shapeX_loc is the same for all processes, + # it is safe to use Scatter + self.comm.Scatter(ff_fft_seq, ff_fft_loc, root=root) + return ff_fft_loc + + # functions for initialisation of field + def constant_arrayK(self, value=None, dtype=complex, SHAPE='LOC'): + """Return a constant array in spectral space.""" + if SHAPE == 'LOC': + shapeK = self.shapeK_loc + elif SHAPE == 'SEQ': + shapeK = self.shapeK_seq + else: + raise ValueError('SHAPE should be ''LOC'' of ''SEQ''') + if value is None: + field_lm = np.empty(self.shapeK, dtype=dtype) + elif value == 0: + field_lm = np.zeros(self.shapeK, dtype=dtype) + else: + field_lm = value*np.ones(self.shapeK, dtype=dtype) + return field_lm + + def constant_arrayX(self, value=None, dtype=DTYPEf, SHAPE='LOC'): + """Return a constant array in real space.""" + if SHAPE == 'LOC': + shapeX = self.shapeX_loc + elif SHAPE == 'SEQ': + shapeX = self.shapeX_seq + else: + raise ValueError('SHAPE should be ''LOC'' of ''SEQ''') + if value is None: + field = np.empty(shapeX, dtype=dtype) + elif value == 0: + field = np.zeros(shapeX, dtype=dtype) + else: + field = value*np.ones(shapeX, dtype=dtype) + return field + + def project_fft_on_realX(self, + np.ndarray[DTYPEc_t, ndim=2] f_fft): + """Project the given field in spectral space such as its + inverse fft is a real field.""" + cdef np.uint32_t nky_seq + cdef np.uint32_t iky_ky0, iky_kyM, ikx_kx0, ikx_kxM, + cdef np.uint32_t ikyp, ikyn + cdef DTYPEc_t f_kp_kx0, f_kn_kx0, f_kp_kxM, f_knp_kxM + + nky_seq = self.shapeK_seq[0] + iky_kyM = nky_seq/2 + + if self.rank == 0: + # first, some values have to be real + f_fft[0, 0] = f_fft[0, 0].real + f_fft[0, iky_kyM] = f_fft[0, iky_kyM].real + + # second, there are relations between some values + for ikyp in xrange(1, iky_kyM): + ikyn = nky_seq - ikyp + + f_kp_kx0 = f_fft[0, ikyp] + f_kn_kx0 = f_fft[0, ikyn] + + f_fft[0, ikyp] = (f_kp_kx0+f_kn_kx0.conjugate() + )/2 + f_fft[0, ikyn] = ((f_kp_kx0+f_kn_kx0.conjugate() + )/2).conjugate() diff --git a/fluidsim/operators/fft/Sources_fftw2dmpiccy/libcfftw2dmpi.c b/fluidsim/operators/fft/Sources_fftw2dmpiccy/libcfftw2dmpi.c new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC9Tb3VyY2VzX2ZmdHcyZG1waWNjeS9saWJjZmZ0dzJkbXBpLmM= --- /dev/null +++ b/fluidsim/operators/fft/Sources_fftw2dmpiccy/libcfftw2dmpi.c @@ -0,0 +1,585 @@ +/* test_fftw3_2Dmpi_simple program + +export USR_PERSO='/home/pierre/usr' +export USR_PERSO='/scratch/augier/usr' + +Compiled with: +mpicc -O3 fft2Dmpisolveq2D.c -I$USR_PERSO/include/ $USR_PERSO/lib/libfftw3.so -lm -o fft2Dmpisolveq2D + +Create the library: +mpicc -c -fPIC fft2Dmpisolveq2D.c -I$USR_PERSO/include -o fft2Dmpisolveq2D.o +mpicc fft2Dmpisolveq2D.o -shared -o libfft2Dmpisolveq2D.so $USR_PERSO/lib/libfftw3.so -lm +mv libfft2Dmpisolveq2D.so $USR_PERSO/lib + + + +And on ferlin: +mpicc -O3 fft2Dmpisolveq2D.c -I${FFTW_HOME}/double/include \ +-L${FFTW_HOME}/double/lib -lfftw3 -lm -o fft2Dmpisolveq2D + +Create the library: +mpicc -c -fPIC fft2Dmpisolveq2D.c -I${FFTW_HOME}/double/include -o fft2Dmpisolveq2D.o + +mpicc -shared -o libfft2Dmpisolveq2D.so fft2Dmpisolveq2D.o \ +${FFTW_HOME}/double/lib/libfftw3.so -lm + + + + + +execute with +mpirun -np 2 ./fft2Dmpisolveq2D +(the 2 after "-np" is the number of processors) +*/ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <complex.h> +#include <fftw3.h> +#include <time.h> +#include <sys/time.h> +#include <mpi.h> + +#include <unistd.h> + +#include <math.h> + +#include "libcfftw2dmpi.h" + + + + +Util_fft init_Util_fft(int N0, int N1) + { + Util_fft uf; + struct timeval start_time, end_time; + /* double total_usecs; */ + int iX0; +/* ii, jj, irank, iX0; */ + int istride = 1, ostride = 1; + int howmany, sign; + MPI_Datatype MPI_type_complex; + + /*DETERMINE RANK OF THIS PROCESSOR*/ + MPI_Comm_rank(MPI_COMM_WORLD, &(uf.rank)); + /*DETERMINE TOTAL NUMBER OF PROCESSORS*/ + MPI_Comm_size(MPI_COMM_WORLD, &(uf.nb_proc)); + + + +/* if ((uf.rank)==0) printf("init_util_fft, N0 = %d, N1 = %d\n", N0, N1);*/ + + MPI_Barrier(MPI_COMM_WORLD); + uf.N0 = N0; + uf.N1 = N1; + + /* y corresponds to dim 0 in physical space */ + /* x corresponds to dim 1 in physical space */ + uf.ny = N0; + uf.nx = N1; + + uf.nX0 = N0; + uf.nX1 = N1; + uf.nX0loc = N0/uf.nb_proc; + uf.nXyloc = uf.nX0loc; + + uf.nKx = uf.nx/2; + uf.nKxloc = uf.nKx/uf.nb_proc; + uf.nKy = uf.ny; + + /* This 2D fft is transposed */ + uf.nK0 = N1/2; + uf.nK0loc = uf.nK0/uf.nb_proc; + uf.nK1 = N0; + + uf.coef_norm = N0*N1; + + uf.flags = FFTW_MEASURE; +/* flags = FFTW_ESTIMATE;*/ +/* uf.flags = FFTW_PATIENT;*/ + + uf.arrayX = (double*) fftw_malloc(sizeof(double)*uf.nX0loc*N1); + uf.arrayK_pR = (fftw_complex*) fftw_malloc( sizeof(fftw_complex) + *uf.nX0loc*(uf.nKx+1)); + uf.arrayK_pC = (fftw_complex*) fftw_malloc(sizeof(fftw_complex)*uf.nKxloc*N0); + + +/* if ((uf.rank)==0) printf("create plans\n");*/ + gettimeofday(&start_time, NULL); +/* plan = fftw_plan_many_dft(int rank, const int *n, int howmany,*/ +/* fftw_complex *in, const int *inembed,*/ +/* int istride, int idist,*/ +/* fftw_complex *out, const int *onembed,*/ +/* int ostride, int odist,*/ +/* int sign, unsigned flags);*/ + + howmany = uf.nX0loc; + uf.plan_r2c = fftw_plan_many_dft_r2c( 1, &N1, howmany, + uf.arrayX, NULL, + istride, N1, + uf.arrayK_pR, NULL, + ostride, uf.nKx+1, + uf.flags); + + uf.plan_c2r = fftw_plan_many_dft_c2r( 1, &N1, howmany, + uf.arrayK_pR, NULL, + istride, uf.nKx+1, + uf.arrayX, NULL, + ostride, N1, + uf.flags); + + howmany = uf.nKxloc; + sign = FFTW_FORWARD; + uf.plan_c2c_fwd = fftw_plan_many_dft( 1, &N0, howmany, + uf.arrayK_pC, &N0, + istride, N0, + uf.arrayK_pC, &N0, + ostride, N0, + sign, uf.flags); + sign = FFTW_BACKWARD; + uf.plan_c2c_bwd = fftw_plan_many_dft( 1, &N0, howmany, + uf.arrayK_pC, &N0, + istride, N0, + uf.arrayK_pC, &N0, + ostride, N0, + sign, uf.flags); + + gettimeofday(&end_time, NULL); + /* total_usecs = (end_time.tv_sec-start_time.tv_sec) + */ + /* (end_time.tv_usec-start_time.tv_usec)/1000000.; */ +/* printf (" done in %f s\n", */ +/* total_usecs);*/ + + for (iX0=0;iX0<uf.nX0loc;iX0++) + uf.arrayK_pR[iX0*(uf.nKx+1)+uf.nKx] = 0.; + + +/* if ((uf.rank)==0) printf("print uf.arrayK_pR\n");*/ +/* for (irank = 0; irank<uf.nb_proc; irank++)*/ +/* {*/ +/* MPI_Barrier(MPI_COMM_WORLD);*/ +/* if (uf.rank == irank)*/ +/* {*/ +/* for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nKx+1; ++jj)*/ +/* {*/ +/* printf("%i , uf.arrayK_pR[%i*(uf.nKx+1) + %i] = (%6.4f, %6.4f)\n", */ +/* uf.rank, ii, jj,*/ +/* creal(uf.arrayK_pR[ii*(uf.nKx+1) + jj]), */ +/* cimag(uf.arrayK_pR[ii*(uf.nKx+1) + jj]));*/ +/* }*/ +/* }*/ +/* else usleep(200);*/ +/* }*/ + + + +/* for (irank = 0; irank<uf.nb_proc; irank++) */ +/* { */ +/* MPI_Barrier(MPI_COMM_WORLD); */ +/* if (uf.rank == irank) */ +/* { */ +/* printf( */ +/* "%i, N0:%i, N1:%i, nX0loc:%i, nXyloc:%i, nKx:%i, nK0:%i, nKy:%i, nK1:%i, nK0loc:%i, nKxloc:%i\n" */ +/* , */ +/* uf.rank, */ +/* N0, N1, */ +/* uf.nX0loc, uf.nXyloc, */ +/* uf.nKx, uf.nK0, */ +/* uf.nKy, uf.nK1, */ +/* uf.nK0loc, */ +/* uf.nKxloc */ +/* ); */ +/* } */ +/* else usleep(200); */ +/* } */ + + + + MPI_Type_contiguous( 2, MPI_DOUBLE, &MPI_type_complex ); + MPI_Type_commit( &MPI_type_complex ); + +/* MPI_Type_vector(int count, int blocklength, int stride, */ +/* MPI_Datatype oldtype, MPI_Datatype *newtype);*/ + + MPI_Type_vector(uf.nX0loc, 1, uf.nKx+1, + MPI_type_complex, &(uf.MPI_type_column)); + MPI_Type_create_resized(uf.MPI_type_column, 0, + sizeof(fftw_complex), + &(uf.MPI_type_column)); + MPI_Type_commit( &(uf.MPI_type_column) ); + + MPI_Type_vector(uf.nKxloc, uf.nX0loc, uf.N0, + MPI_type_complex, &(uf.MPI_type_block)); + MPI_Type_create_resized(uf.MPI_type_block, 0, + uf.nX0loc*sizeof(fftw_complex), + &(uf.MPI_type_block)); + MPI_Type_commit( &(uf.MPI_type_block) ); + + + return uf; +} + + + +void destroy_Util_fft(Util_fft uf) + { +/* if ((uf.rank)==0) printf("destroy_util_fft\n");*/ + fftw_destroy_plan(uf.plan_r2c); + fftw_destroy_plan(uf.plan_c2c_fwd); + fftw_destroy_plan(uf.plan_c2c_bwd); + fftw_destroy_plan(uf.plan_c2r); + fftw_free(uf.arrayX); + fftw_free(uf.arrayK_pR); + fftw_free(uf.arrayK_pC); + MPI_Type_free(&(uf.MPI_type_column)); + MPI_Type_free(&(uf.MPI_type_block)); +} + + + + + + + + + +void fft2D(Util_fft uf, double *fieldX, fftw_complex *fieldK) + { + int ii; +/* , jj, irank; */ + /*use memcpy(void * destination, void * source, size_t bytes); */ + + memcpy(uf.arrayX, fieldX, uf.nX0loc*uf.nX1*sizeof(double)); + +/* if ((uf.rank)==0) printf("print uf.arrayX\n");*/ +/* for (irank = 0; irank<uf.nb_proc; irank++)*/ +/* {*/ +/* MPI_Barrier(MPI_COMM_WORLD);*/ +/* if (uf.rank == irank)*/ +/* {*/ +/* for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.N1; ++jj)*/ +/* {*/ +/* printf( "%d , uf.arrayX[%d,%d] = %6.4f\n", */ +/* uf.rank, ii, jj, uf.arrayX[ii*uf.N1+jj]);*/ +/* }*/ +/* }*/ +/* else usleep(200);*/ +/* }*/ +/* MPI_Barrier(MPI_COMM_WORLD);*/ + + fftw_execute(uf.plan_r2c); + +/* if ((uf.rank)==0) printf("print uf.arrayK_pR after alltoall\n");*/ +/* for (irank = 0; irank<uf.nb_proc; irank++)*/ +/* {*/ +/* MPI_Barrier(MPI_COMM_WORLD);*/ +/* if (uf.rank == irank)*/ +/* {*/ +/* for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nKx+1; ++jj)*/ +/* {*/ +/* printf("%i , uf.arrayK_pR[%i*(uf.nKx+1) + %i] = (%6.4f, %6.4f)\n", */ +/* uf.rank, ii, jj,*/ +/* creal(uf.arrayK_pR[ii*(uf.nKx+1)+jj]), */ +/* cimag(uf.arrayK_pR[ii*(uf.nKx+1)+jj]));*/ +/* }*/ +/* }*/ +/* else usleep(200);*/ +/* MPI_Barrier(MPI_COMM_WORLD);*/ +/* }*/ +/* MPI_Barrier(MPI_COMM_WORLD);*/ + +/* second step: alltoall communication...*/ + MPI_Alltoall(uf.arrayK_pR, uf.nKxloc, uf.MPI_type_column, + uf.arrayK_pC, 1, uf.MPI_type_block, + MPI_COMM_WORLD); + + +/* if ((uf.rank)==0) printf("print uf.arrayK_pC after alltoall\n");*/ +/* for (irank = 0; irank<uf.nb_proc; irank++)*/ +/* {*/ +/* MPI_Barrier(MPI_COMM_WORLD);*/ +/* if (uf.rank == irank)*/ +/* {*/ +/* for (ii = 0; ii < uf.nKxloc; ++ii) for (jj = 0; jj < uf.N0; ++jj)*/ +/* {*/ +/* printf("%i , uf.arrayK_pC[%i*uf.N0 + %i] = (%6.4f, %6.4f)\n", */ +/* uf.rank, ii, jj,*/ +/* creal(uf.arrayK_pC[ii*uf.N0 + jj]), */ +/* cimag(uf.arrayK_pC[ii*uf.N0 + jj]));*/ +/* }*/ +/* }*/ +/* else usleep(200);*/ +/* MPI_Barrier(MPI_COMM_WORLD);*/ +/* }*/ +/* MPI_Barrier(MPI_COMM_WORLD);*/ + + + fftw_execute(uf.plan_c2c_fwd); + +/* if ((uf.rank)==0) printf("print uf.arrayK_pC after fftw_execute\n");*/ +/* for (irank = 0; irank<uf.nb_proc; irank++)*/ +/* {*/ +/* MPI_Barrier(MPI_COMM_WORLD);*/ +/* if (uf.rank == irank)*/ +/* {*/ +/* for (ii = 0; ii < uf.nKxloc; ++ii) for (jj = 0; jj < uf.N0; ++jj)*/ +/* {*/ +/* printf("%i , uf.arrayK_pC[%i*uf.N0 + %i] = (%6.4f, %6.4f)\n", */ +/* uf.rank, ii, jj,*/ +/* creal(uf.arrayK_pC[ii*uf.N0 + jj]), */ +/* cimag(uf.arrayK_pC[ii*uf.N0 + jj]));*/ +/* }*/ +/* }*/ +/* else usleep(200);*/ +/* MPI_Barrier(MPI_COMM_WORLD);*/ +/* }*/ +/* MPI_Barrier(MPI_COMM_WORLD);*/ + + + for (ii=0; ii<uf.nKxloc*uf.nKy; ii++) + fieldK[ii] = uf.arrayK_pC[ii]/uf.coef_norm; + + } + + + + +void ifft2D(Util_fft uf, fftw_complex *fieldK, double *fieldX) + { + int ii; +/* , jj, irank; */ + /*use memcpy(void * destination, void * source, size_t bytes); */ + memcpy(uf.arrayK_pC, fieldK, uf.nKxloc*uf.nKy*sizeof(fftw_complex)); + fftw_execute(uf.plan_c2c_bwd); + MPI_Alltoall( uf.arrayK_pC, 1, uf.MPI_type_block, + uf.arrayK_pR, uf.nKxloc, uf.MPI_type_column, + MPI_COMM_WORLD); + + /*These modes (nx/2+1=N1/2+1) have to be settled to zero*/ + for (ii = 0; ii < uf.nX0loc; ++ii) + uf.arrayK_pR[ii*(uf.nKx+1) + uf.nKx] = 0.; + + fftw_execute(uf.plan_c2r); + memcpy(fieldX,uf.arrayX, uf.nX0loc*uf.nX1*sizeof(double)); + } + + +void time_execute(Util_fft uf, double *fieldX, fftw_complex *fieldK, int nb_time_execute) + { + int ii; + struct timeval start_time, end_time; + double total_usecs; + + if (uf.rank==0) printf("timer...\n"); + gettimeofday(&start_time, NULL); + for (ii=0; ii<nb_time_execute; ii++) + { + fft2D(uf, fieldX, fieldK); + ifft2D(uf, fieldK, fieldX); + } + gettimeofday(&end_time, NULL); + + + total_usecs = (end_time.tv_sec-start_time.tv_sec) + + (end_time.tv_usec-start_time.tv_usec)/1000000.; + printf ("%d times forward and backward sequencial FFT: %f s\n", + nb_time_execute ,total_usecs); +} + + + +int main(int argc, char **argv) + { + const int N0 = 32*4*2, N1 = 32*4*2; + Util_fft uf; + double * fieldX; +/* , * fieldX_0; */ + fftw_complex * fieldK; +/* , * fieldK_0; */ + int ii, jj; +/* , irank; */ + double energyK, energyX, energy2; + int coef; + + + MPI_Init(&argc, &argv); + uf = init_Util_fft(N0,N1); + + srand(time(NULL)+uf.rank*uf.nb_proc); + + printf( "I'm rank (processor number) %i of size %i\n", + uf.rank, uf.nb_proc); + + fieldX = (double *) malloc(uf.nX0loc*uf.nX1 * sizeof(double)); + /* fieldX_0 = (double *) malloc(uf.nX0loc*uf.nX1 * sizeof(double)); */ + fieldK = (fftw_complex *) malloc(uf.nKxloc*uf.nKy * sizeof(fftw_complex)); + /* fieldK_0 = (fftw_complex *) malloc(uf.nKxloc*uf.nKy * sizeof(fftw_complex)); */ + + +/* time_execute(uf, fieldX, fieldK, 100);*/ + + + + for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nX1; ++jj) + { +/* fieldX[ii*uf.nX1+jj] = 2.;*/ + fieldX[ii*uf.nX1+jj] = rand()/(double)RAND_MAX -0.5; + } + + for (ii = 0; ii < uf.nKxloc; ++ii) for (jj = 0; jj < uf.nKy; ++jj) + { + fieldK[ii*uf.nKy+jj] = 0.; + } + +/* We have to project on the space available for this library */ + fft2D(uf, fieldX, fieldK); + ifft2D(uf, fieldK, fieldX); + + + + /* for (ii = 0; ii < uf.nKxloc; ++ii) for (jj = 0; jj < uf.nKy; ++jj) */ + /* fieldK_0[ii*uf.nKy+jj] = fieldK[ii*uf.nKy+jj]; */ + + /* for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nX1; ++jj) */ + /* fieldX_0[ii*uf.nX1+jj] = fieldX[ii*uf.nX1+jj]; */ + + + + /* if (uf.rank==0) */ + /* { */ +/* fieldK[0*uf.nKy+0] = 1.;*/ + +/* fieldK[0*uf.nKy+1] = 1.I;*/ +/* fieldK[0*uf.nKy+3] = -1.I;*/ + + /* fieldK[0*uf.nKy+2] = 1.; */ + /* } */ + + +/* if (uf.rank==1) */ +/* {*/ +/* fieldK[0*uf.nKy+0] = 1.;*/ + +/* fieldK[0*uf.nKy+1] = 1.I;*/ +/* fieldK[0*uf.nKy+3] = -1.I;*/ + +/* fieldK[0*uf.nKy+2] = 1.I;*/ +/* }*/ + + + + + + + + + /* time_execute(uf, fieldX, fieldK, 100); */ + + + /* usleep(100); */ + /* for (irank = 0; irank<uf.nb_proc; irank++) */ + /* { */ + /* MPI_Barrier(MPI_COMM_WORLD); */ + /* if (uf.rank == irank) */ + /* { */ + /* for (ii = 0; ii < uf.nKxloc; ++ii) for (jj = 0; jj < uf.nKy; ++jj) */ + /* { */ + /* printf("%i , fieldK[%i*nKxloc + %i] = (%6.4f, %6.4f)\n", */ + /* uf.rank, ii, jj, */ + /* creal(fieldK[ii*uf.nKy + jj]), */ + /* cimag(fieldK[ii*uf.nKy + jj])); */ + /* } */ + /* } */ + /* else usleep(100); */ + /* MPI_Barrier(MPI_COMM_WORLD); */ + /* } */ + /* MPI_Barrier(MPI_COMM_WORLD); */ + + + + + + + + /* for (irank = 0; irank<uf.nb_proc; irank++) */ + /* { */ + /* MPI_Barrier(MPI_COMM_WORLD); */ + /* if (uf.rank == irank) */ + /* { */ + /* for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nX1; ++jj) */ + /* { */ + /* printf( "%d , fieldX[%d,%d] = %+6.4f\n", */ + /* uf.rank, ii, jj, fieldX[ii*uf.nX1+jj]); */ + /* } */ + /* } */ + /* MPI_Barrier(MPI_COMM_WORLD); */ + /* } */ + /* MPI_Barrier(MPI_COMM_WORLD); */ + + /* for (irank = 0; irank<uf.nb_proc; irank++) */ + /* { */ + /* MPI_Barrier(MPI_COMM_WORLD); */ + /* if (uf.rank == irank) */ + /* { */ + /* for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nX1; ++jj) */ + /* { */ + /* printf("%d , (fieldX - fieldX_0)[%d,%d] = %+6.4f\n", */ + /* uf.rank, ii, jj, */ + /* fieldX[ii*uf.nX1+jj]-fieldX_0[ii*uf.nX1+jj]); */ + /* } */ + /* } */ + /* MPI_Barrier(MPI_COMM_WORLD); */ + /* } */ + /* MPI_Barrier(MPI_COMM_WORLD); */ + + + + + + + + + energyX = 0.; + for (ii = 0; ii < uf.nX0loc; ++ii) for (jj = 0; jj < uf.nX1; ++jj) + { + + energyX += fieldX[ii*uf.nX1+jj]*fieldX[ii*uf.nX1+jj]; + } + energyX = energyX/uf.coef_norm; + energy2 = energyX; + MPI_Reduce(&energy2, &energyX, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); + if (uf.rank==0) printf( "energyX = %8.6f\n", energyX); + + + energyK = 0.; + for (ii = 0; ii < uf.nKxloc; ++ii) for (jj = 0; jj < uf.nKy; ++jj) + { + if ((uf.rank==0) & (ii==0)) + coef = 1; + else + coef = 2; + energyK += pow(cabs(fieldK[ii*uf.nKy + jj]), 2) *coef; + } + energy2 = energyK; + + MPI_Reduce(&energy2, &energyK, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); + if (uf.rank==0) printf( "energyK = %8.6f\n", energyK); + + + + + + + free(fieldX); + /* free(fieldX_0); */ + free(fieldK); + + destroy_Util_fft(uf); + MPI_Finalize(); + + return 0; + } + + + diff --git a/fluidsim/operators/fft/Sources_fftw2dmpiccy/libcfftw2dmpi.h b/fluidsim/operators/fft/Sources_fftw2dmpiccy/libcfftw2dmpi.h new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC9Tb3VyY2VzX2ZmdHcyZG1waWNjeS9saWJjZmZ0dzJkbXBpLmg= --- /dev/null +++ b/fluidsim/operators/fft/Sources_fftw2dmpiccy/libcfftw2dmpi.h @@ -0,0 +1,28 @@ + + +typedef struct +{ + /* X and K denote physical and Fourier spaces. */ + /* y corresponds to dim 0 in physical space */ + /* x corresponds to dim 1 in physical space */ + int N0, N1, nX0, nX1, nX0loc; + int ny, nx, nXyloc; + /* y corresponds to dim 1 in Fourier space */ + /* x corresponds to dim 0 in Fourier space */ + int nK0, nK1, nK0loc; + int nKx, nKy, nKxloc; + int coef_norm; + fftw_plan plan_r2c, plan_c2c_fwd, plan_c2r, plan_c2c_bwd; + double *arrayX; + fftw_complex *arrayK_pR, *arrayK_pC; + + unsigned flags; + int rank, nb_proc, irank; + MPI_Datatype MPI_type_column, MPI_type_block; +} Util_fft; + +Util_fft init_Util_fft(int N0, int N1); +void destroy_Util_fft(Util_fft uf); +void fft2D(Util_fft uf, double *fieldX, fftw_complex *fieldK); +void ifft2D(Util_fft uf, fftw_complex *fieldK, double *fieldX); + diff --git a/fluidsim/operators/fft/Sources_fftw2dmpiccy/libcfftw2dmpi.pxd b/fluidsim/operators/fft/Sources_fftw2dmpiccy/libcfftw2dmpi.pxd new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC9Tb3VyY2VzX2ZmdHcyZG1waWNjeS9saWJjZmZ0dzJkbXBpLnB4ZA== --- /dev/null +++ b/fluidsim/operators/fft/Sources_fftw2dmpiccy/libcfftw2dmpi.pxd @@ -0,0 +1,35 @@ + +from mpi4py cimport MPI + +cdef extern from "complex.h": + pass + +cdef extern from "fftw3.h": + ctypedef struct fftw_plan_s: + pass + ctypedef fftw_plan_s *fftw_plan + +cdef extern from "libcfftw2dmpi.h": + ctypedef struct Util_fft: + # X and K denote physical and Fourier spaces + # y corresponds to dim 0 in physical space + # x corresponds to dim 1 in physical space + int N0, N1, nX0, nX1, nX0loc + int ny, nx, nXyloc + # y corresponds to dim 1 in Fourier space + # x corresponds to dim 0 in Fourier space + int nK0, nK1, nK0loc + int nKx, nKy, nKxloc + int coef_norm + fftw_plan plan_r2c, plan_c2c_fwd, plan_c2r, plan_c2c_bwd + double *arrayX + complex *arrayK_pR + complex *arrayK_pC + unsigned flags + int rank, nb_proc, irank + MPI.MPI_Datatype MPI_type_column, MPI_type_block + + Util_fft init_Util_fft(int N0, int N1) + void destroy_Util_fft(Util_fft uf) + void fft2D(Util_fft uf, double *fieldX, complex *fieldK) + void ifft2D(Util_fft uf, complex *fieldK, double *fieldX) diff --git a/fluidsim/operators/fft/Sources_fftw2dmpicy/fftw2dmpicy.pyx b/fluidsim/operators/fft/Sources_fftw2dmpicy/fftw2dmpicy.pyx new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC9Tb3VyY2VzX2ZmdHcyZG1waWN5L2ZmdHcyZG1waWN5LnB5eA== --- /dev/null +++ b/fluidsim/operators/fft/Sources_fftw2dmpicy/fftw2dmpicy.pyx @@ -0,0 +1,594 @@ + + +from __future__ import division, print_function + +from time import sleep + +cimport numpy as np +import numpy as np +np.import_array() + + +try: + from mpi4py import MPI +except ImportError: + nb_proc = 1 + rank = 0 +else: + comm = MPI.COMM_WORLD + nb_proc = comm.size + rank = comm.Get_rank() + +IF MPI4PY: + from mpi4py cimport MPI + from mpi4py.mpi_c cimport * + + # solve an incompatibility between openmpi and mpi4py versions + cdef extern from 'mpi-compat.h': + pass + +from cpython cimport Py_INCREF + +cimport libc +from libc.stddef cimport ptrdiff_t + +cimport fftw3 +from fftw3 cimport fftw_iodim, FFTW_FORWARD, FFTW_BACKWARD, \ + FFTW_MEASURE, FFTW_DESTROY_INPUT, FFTW_UNALIGNED, \ + FFTW_CONSERVE_MEMORY, FFTW_EXHAUSTIVE, FFTW_PRESERVE_INPUT, \ + FFTW_PATIENT, FFTW_ESTIMATE, FFTW_MPI_TRANSPOSED_IN, \ + FFTW_MPI_TRANSPOSED_OUT, fftw_plan, FFTW_MPI_DEFAULT_BLOCK + +IF MPI4PY: + cimport fftw3mpi + + +fftw_flags = {'FFTW_CONSERVE_MEMORY': FFTW_CONSERVE_MEMORY, + 'FFTW_DESTROY_INPUT': FFTW_DESTROY_INPUT, + 'FFTW_ESTIMATE': FFTW_ESTIMATE, + 'FFTW_EXHAUSTIVE': FFTW_EXHAUSTIVE, + 'FFTW_MEASURE': FFTW_MEASURE, + 'FFTW_MPI_TRANSPOSED_IN': FFTW_MPI_TRANSPOSED_IN, + 'FFTW_MPI_TRANSPOSED_OUT': FFTW_MPI_TRANSPOSED_OUT, + 'FFTW_PATIENT': FFTW_PATIENT, + 'FFTW_PRESERVE_INPUT': FFTW_PRESERVE_INPUT, + 'FFTW_UNALIGNED': FFTW_UNALIGNED} + +from cpython.ref cimport PyTypeObject + +cdef extern from "numpy/arrayobject.h": + object PyArray_NewFromDescr(PyTypeObject * subtype, np.dtype descr, + int nd, np.npy_intp* dims, np.npy_intp* strides, + void* data, int flags, object obj) + object PyArray_SimpleNewFromData(int nd, int* dims, int typenum,void* data) + + +# we define python and c types for physical and Fourier spaces +DTYPEb = np.uint8 +ctypedef np.uint8_t DTYPEb_t +DTYPEi = np.int +ctypedef np.int_t DTYPEi_t +DTYPEf = np.float64 +ctypedef np.float64_t DTYPEf_t +DTYPEc = np.complex128 +ctypedef np.complex128_t DTYPEc_t + + +cdef class FFT2Dmpi(object): + """The FFT2Dmpi class is a cython wrapper for the 2D fast Fourier + transform (sequencial and MPI) of the fftw library.""" + # number of nodes in the first and second dimensions + cdef int n0, n1 + # flags for fftw + cdef int flags + cdef public DTYPEb_t TRANSPOSED, SEQUENCIAL + + # coef for normalization + cdef int coef_norm + + cdef np.ndarray arrayX, arrayK + + cdef complex *carrayK + cdef DTYPEf_t *carrayX + + cdef fftw_plan plan_forward + cdef fftw_plan plan_backward + + # shape of the arrays in the physical and Fourier spaces, + # for the sequential case: + cdef public np.ndarray shapeX_seq, shapeK_seq, shapeK_gat + # and for the parallel case: + cdef public np.ndarray shapeX_loc, shapeK_loc, shapeX_locpad + + # the communicator, nb of processus and rank of the processus + IF MPI4PY: + cdef MPI.Comm comm + + cdef public int nb_proc, rank + + cdef public ptrdiff_t iX0loc_start, iKxloc_start, iKyloc_start + + cdef public int idimx, idimy, idimkx, idimky + + cdef size_t n_alloc_local + + def __init__(self, int n0, int n1, flags=['FFTW_MEASURE'], + TRANSPOSED=True, SEQUENCIAL=None): + + if TRANSPOSED is None: + TRANSPOSED = True + + if nb_proc == 1 or SEQUENCIAL: + self.SEQUENCIAL = True + if SEQUENCIAL and rank == 0 and nb_proc > 1: + print(' sequencial version even though self.nb_proc > 1') + else: + self.SEQUENCIAL = False + + # info on MPI + self.nb_proc = nb_proc + self.rank = rank + if self.nb_proc > 1: + self.comm = comm + + if n0 % 2 != 0 or n1 % 2 != 0: + raise ValueError('conditions n0 and n1 even not fulfill') + + if not self.SEQUENCIAL and n0//2 + 1 < nb_proc: + raise ValueError('condition nx//2+1 >= nb_proc not fulfill') + + self.n0 = n0 + self.n1 = n1 + + self.shapeX_seq = np.array([n0, n1]) + self.shapeK_seq = np.array([n0, n1//2+1]) + + # print('shapeX_seq:', shapeX_seq, '\nshapeK_seq:', shapeK_seq) + + if self.nb_proc == 1 or SEQUENCIAL: + TRANSPOSED = False + + self.TRANSPOSED = TRANSPOSED + + # we consider that the first dimension corresponds to the x-axes. + # and the second dimension corresponds to the y-axes. + self.idimx = 1 + self.idimy = 0 + + if self.TRANSPOSED: + self.idimkx = 0 + self.idimky = 1 + self.shapeK_gat = np.array([n1//2+1, n0]) + else: + self.idimkx = 1 + self.idimky = 0 + self.shapeK_gat = np.array([n0, n1//2+1]) + + for f in flags: + self.flags = self.flags | fftw_flags[f] + + self.coef_norm = n0*n1 + + # Allocate the carrays and create the plans + # and create the np arrays pointing to the carrays + if self.nb_proc == 1 or SEQUENCIAL: + self.init_seq() + else: + self.init_parall() + + cdef init_seq(self): + """ + Allocate the carrays, create the plans (for sequential FFTW) + and create np arrays pointing on the carrays + """ + + # print('init_seq:', self.shapeX_seq, self.shapeK_seq) + + # from fluiddyn.util.debug_with_ipython import ipydebug + # ipydebug() + + self.shapeX_loc = self.shapeX_seq + self.shapeK_loc = self.shapeK_seq + self.iKxloc_start = 0 + self.iKyloc_start = 0 + self.n_alloc_local = self.shapeK_loc.prod() + + # print('self.n_alloc_local:', self.n_alloc_local) + + self.carrayK = fftw3.fftw_alloc_complex(<size_t> self.shapeK_loc.prod()) + self.carrayX = fftw3.fftw_alloc_real(<size_t> self.shapeX_loc.prod()) + + # print('after alloc') + + self.plan_forward = fftw3.fftw_plan_dft_r2c_2d(self.n0, self.n1, + <double*> self.carrayX, + <complex*> self.carrayK, + self.flags) + self.plan_backward = fftw3.fftw_plan_dft_c2r_2d(self.n0, self.n1, + <complex*> self.carrayK, + <double*> self.carrayX, + self.flags) + + # print(self.n0, self.n1, self.shapeX_loc.data) + # print('after planning, self.flags:', self.flags) + + self.arrayX = PyArray_SimpleNewFromData( + <int> 2, <np.npy_intp *> self.shapeX_loc.data, + np.NPY_FLOAT64, <void*> self.carrayX) + # print('after first PyArray_SimpleNewFromData') + + self.arrayK = PyArray_SimpleNewFromData( + <int> 2, <np.npy_intp *> self.shapeK_loc.data, + np.NPY_COMPLEX128, <void*> self.carrayK) + + # print('after PyArray_SimpleNewFromData') + + IF MPI4PY: + + cdef init_parall(self): + """ + Allocate the carrays, create the plans (for MPI FFTW) + and create the np arrays pointing to the carrays + """ + cdef MPI_Comm c_comm = self.comm.ob_mpi + cdef int flags_temp + cdef size_t n_alloc_local + cdef ptrdiff_t nX0loc, nKxloc + + # if self.rank==0: print 'self.init_parall()' + fftw3mpi.fftw_mpi_init() + + if not self.TRANSPOSED: + n_alloc_local = fftw3mpi.fftw_mpi_local_size_2d( + <size_t> self.n0, <size_t> self.n1//2+1, + c_comm, + &nX0loc, &self.iX0loc_start) + self.shapeK_loc = np.array([nX0loc, self.n1//2+1]) + self.iKxloc_start = 0 + self.iKyloc_start = self.iX0loc_start + else: + n_alloc_local = fftw3mpi.fftw_mpi_local_size_2d_transposed( + <size_t> self.n0, <size_t> self.n1//2+1, + c_comm, + &nX0loc, &self.iX0loc_start, + &nKxloc, &self.iKxloc_start) + self.shapeK_loc = np.array([nKxloc, self.n0]) + self.iKyloc_start = 0 + + self.n_alloc_local = n_alloc_local + + self.shapeX_loc = np.array([nX0loc, self.n1]) + + self.shapeX_locpad = self.shapeX_loc.copy() + self.shapeX_locpad[-1] = 2*(self.shapeX_loc[-1]//2+1) + + self.carrayK = fftw3.fftw_alloc_complex(n_alloc_local) + self.carrayX = fftw3.fftw_alloc_real(2 * n_alloc_local) + + #### for r in xrange(self.nb_proc): + #### self.comm.barrier() + #### sleep(0.05) + #### if self.rank==r: + #### print 'rank =', self.rank, 'n_alloc_local =', n_alloc_local,\ + #### 'other:', nX0loc, self.iX0loc_start,\ + #### self.iKxloc_start, self.iKyloc_start + #### print 'self.shapeX_locpad =', self.shapeX_locpad + #### print 'self.shapeX_loc =', self.shapeX_loc + #### print 'self.shapeK_loc =', self.shapeK_loc + + if self.TRANSPOSED: + flags_temp = self.flags | fftw_flags['FFTW_MPI_TRANSPOSED_OUT'] + else: + flags_temp = self.flags + self.plan_forward = fftw3mpi.fftw_mpi_plan_dft_r2c_2d( + <size_t> self.n0, <size_t> self.n1, + <double*> self.carrayX, + <complex*> self.carrayK, + c_comm, + flags_temp) + + if self.TRANSPOSED: + flags_temp = self.flags | fftw_flags['FFTW_MPI_TRANSPOSED_IN'] + else: + flags_temp = self.flags + self.plan_backward = fftw3mpi.fftw_mpi_plan_dft_c2r_2d( + <size_t> self.n0, <size_t> self.n1, + <complex*> self.carrayK, + <double*> self.carrayX, + c_comm, + flags_temp) + + # create the python arrays (arrayX and arrayK) pointing + # toward carrayX and carrayK + cdef np.dtype npDTYPEf = np.dtype('float64') + Py_INCREF(npDTYPEf) + cdef np.ndarray[DTYPEi_t, ndim=1] stridesX + # stridesX en nb elements + stridesX = np.array([self.shapeX_locpad[1], 1]) + # stridesX en octet + stridesX *= npDTYPEf.itemsize + cdef int ndim = 2 + self.arrayX = PyArray_NewFromDescr( + <PyTypeObject *> np.ndarray, + npDTYPEf, ndim, + <np.npy_intp *> self.shapeX_loc.data, + <np.npy_intp *> stridesX.data, + <void *> self.carrayX, + np.NPY_DEFAULT, None) + + self.arrayK = PyArray_SimpleNewFromData( + <int> 2, <np.npy_intp *> self.shapeK_loc.data, + np.NPY_COMPLEX128, <void*> self.carrayK) + + # cpdef fft2d(self, np.ndarray[DTYPEf_t, ndim=2] ff): + cpdef fft2d(self, DTYPEf_t[:, :] ff): + self.arrayX[:] = ff + fftw3.fftw_execute(self.plan_forward) + return self.arrayK/self.coef_norm + + # cpdef ifft2d(self, np.ndarray[DTYPEc_t, ndim=2] ff_fft): + cpdef ifft2d(self, DTYPEc_t[:, :] ff_fft): + """Inverse Fast Fourier Transform 2D + + This is THE function where most of the time is spent ! + """ + # print ff_fft + self.arrayK[:] = ff_fft + # self.print_carrayK() + fftw3.fftw_execute(self.plan_backward) + # self.print_carrayX() + # print self.arrayX +#### result = self.arrayX.copy() # BUG with pelvoux !!! (python 2.6) +#### result = self.arrayX*1 # works, but maybe slower ? + return self.arrayX.copy() + + def __dealloc__(self): + if self.nb_proc == 1: + fftw3.fftw_destroy_plan(self.plan_forward) + fftw3.fftw_destroy_plan(self.plan_backward) + fftw3.fftw_free(self.carrayX) + fftw3.fftw_free(self.carrayK) + else: + IF MPI4PY: + fftw3mpi.fftw_mpi_cleanup() + + def print_carrayX(self): + cdef int ii, r + for r in xrange(self.nb_proc): + self.comm.barrier() + sleep(0.05) + if self.rank == r: + for ii in xrange(2*self.n_alloc_local): + print(self.rank, ii, + ' self.carrayX[ii] =', self.carrayX[ii]) + + def print_carrayK(self): + cdef int ii, r + for r in xrange(self.nb_proc): + self.comm.barrier() + sleep(0.05) + if self.rank == r: + for ii in xrange(self.n_alloc_local): + print(self.rank, ii, ' self.carrayK[ii] =', + self.carrayK[ii]) + + def describe(self): + if self.rank == 0: + print('object of class ', self.__class__, + '\nn0 =', self.n0, 'n1 =', self.n1, + '\nTRANSPOSED =', self.TRANSPOSED, + '\nnb_proc =', self.nb_proc) + if self.nb_proc == 1: + print('=> sequenciel version') + else: + print('=> parallel version (MPI)') + + def gather_Xspace(self, np.ndarray ff_loc, + root=None, type DTYPEf=None): + cdef np.ndarray ff_seq + if DTYPEf is None: + DTYPEf = DTYPEf + # self.shapeX_loc is the same for all processes, + # it is safe to use Allgather or Gather + if root is None: + ff_seq = np.empty(self.shapeX_seq, DTYPEf) + self.comm.Allgather(ff_loc, ff_seq) + elif isinstance(root, int): + ff_seq = None + if self.rank == root: + ff_seq = np.empty(self.shapeX_seq, DTYPEf) + self.comm.Gather(ff_loc, ff_seq, root=root) + else: + raise ValueError('root should be an int') + return ff_seq + + def scatter_Xspace(self, np.ndarray ff_seq, + int root=0, type DTYPEf=None): + cdef np.ndarray ff_loc + if DTYPEf is None: + DTYPEf = DTYPEf + ff_loc = np.empty(self.shapeX_loc, dtype=DTYPEf) + # self.shapeX_loc is the same for all processes, + # it is safe to use Scatter + if isinstance(root, int): + self.comm.Scatter(ff_seq, ff_loc, root=root) + else: + raise ValueError('root should be an int') + return ff_loc + + def gather_Kspace(self, np.ndarray ff_fft_loc, root=None): + cdef np.ndarray ff_fft_seq + cdef np.ndarray shapeK_temp + + if self.TRANSPOSED is True: + shapeK_temp = np.empty(2) + shapeK_temp[0] = self.shapeK_seq[1] + shapeK_temp[1] = self.shapeK_seq[0] + else: + shapeK_temp = self.shapeK_seq + rowtype = MPI.COMPLEX16.Create_contiguous(self.shapeK_loc[1]) + rowtype.Commit() + if root is None: + ff_fft_seq = np.empty(shapeK_temp, dtype=DTYPEc) + counts = self.comm.allgather(self.shapeK_loc[0]) + self.comm.Allgatherv(sendbuf=[ff_fft_loc, MPI.COMPLEX16], + recvbuf=[ff_fft_seq, (counts, None), rowtype]) + if self.TRANSPOSED is True: + ff_fft_seq = ff_fft_seq.transpose() + elif isinstance(root, int): + ff_fft_seq = None + if self.rank == root: + ff_fft_seq = np.empty(shapeK_temp, dtype=DTYPEc) + counts = self.comm.gather(self.shapeK_loc[0], root=root) + self.comm.Gatherv(sendbuf=[ff_fft_loc, MPI.COMPLEX16], + recvbuf=[ff_fft_seq, (counts, None), rowtype], + root=root) + if self.rank == root and (self.TRANSPOSED is True): + ff_fft_seq = ff_fft_seq.transpose() + else: + raise ValueError('root should be an int') + rowtype.Free() + return ff_fft_seq + + + # ATTENTION C'EST FAUX !!!!!! + # C'EST LA VERSION DE CCYLIB !!!!!! + # IL FAUDRA ECRIRE CA COMME IL FAUT + def scatter_Kspace(self, np.ndarray ff_fft_seq, int root=0, + AS_SEQ=True, type DTYPE=DTYPEc): + cdef np.ndarray ff_fft_loc + cdef np.ndarray shapeK_temp + + if not isinstance(root, int): + raise ValueError('root should be an int') + + if AS_SEQ and root == self.rank: + ff_fft_seq = ff_fft_seq[:, :-1] + ff_fft_seq = ff_fft_seq.transpose() + + ff_fft_loc = np.empty(self.shapeK_loc, dtype=DTYPE) + # self.shapeX_loc is the same for all processes, + # it is safe to use Scatter + + # NO !!!!!!!!!! + + self.comm.Scatter(ff_fft_seq, ff_fft_loc, root=root) + return ff_fft_loc + + # functions for initialisation of field + def constant_arrayK(self, value=None, dtype=complex, SHAPE='LOC'): + """Return a constant array in spectral space.""" + if SHAPE == 'LOC': + shapeK = self.shapeK_loc + elif SHAPE == 'SEQ': + shapeK = self.shapeK_seq + else: + raise ValueError('SHAPE should be ''LOC'' of ''SEQ''') + if value == None: + field_lm = np.empty(self.shapeK, dtype=dtype) + elif value == 0: + field_lm = np.zeros(self.shapeK, dtype=dtype) + else: + field_lm = value*np.ones(self.shapeK, dtype=dtype) + return field_lm + + def constant_arrayX(self, value=None, dtype=DTYPEf, SHAPE='LOC'): + """Return a constant array in real space.""" + if SHAPE == 'LOC': + shapeX = self.shapeX_loc + elif SHAPE == 'SEQ': + shapeX = self.shapeX_seq + else: + raise ValueError('SHAPE should be ''LOC'' of ''SEQ''') + if value is None: + field = np.empty(shapeX, dtype=dtype) + elif value == 0: + field = np.zeros(shapeX, dtype=dtype) + else: + field = value*np.ones(shapeX, dtype=dtype) + return field + + def compute_energy_from_Fourier(self, ff_fft): + if self.nb_proc > 1: + raise ValueError('not yet implemented for mpi') + return (np.sum(abs(ff_fft[:, 0])**2 + abs(ff_fft[:, -1])**2) + + 2*np.sum(abs(ff_fft[:, 1:-1])**2))/2 + + def compute_energy_from_spatial(self, ff): + if self.nb_proc > 1: + raise ValueError('not yet implemented for mpi') + + return np.mean(abs(ff)**2)/2 + + def project_fft_on_realX(self, + np.ndarray[DTYPEc_t, ndim=2] f_fft): + """Project the given field in spectral space such as its + inverse fft is a real field.""" + cdef np.uint32_t nky_seq + cdef np.uint32_t iky_ky0, iky_kyM, ikx_kx0, ikx_kxM, + cdef np.uint32_t ikyp, ikyn + cdef DTYPEc_t f_kp_kx0, f_kn_kx0, f_kp_kxM, f_knp_kxM + + if self.nb_proc == 1 or self.SEQUENCIAL: + nky_seq = self.shapeK_seq[0] + + iky_ky0 = 0 + iky_kyM = nky_seq//2 + ikx_kx0 = 0 + ikx_kxM = self.shapeK_seq[1]-1 + + # first, some values have to be real + f_fft[iky_ky0, ikx_kx0] = f_fft[iky_ky0, ikx_kx0].real + f_fft[iky_ky0, ikx_kxM] = f_fft[iky_ky0, ikx_kxM].real + f_fft[iky_kyM, ikx_kx0] = f_fft[iky_kyM, ikx_kx0].real + f_fft[iky_kyM, ikx_kxM] = f_fft[iky_kyM, ikx_kxM].real + + # second, there are relations between some values + for ikyp in xrange(1, iky_kyM): + ikyn = nky_seq - ikyp + + f_kp_kx0 = f_fft[ikyp, ikx_kx0] + f_kn_kx0 = f_fft[ikyn, ikx_kx0] + f_fft[ikyp, ikx_kx0] = (f_kp_kx0+f_kn_kx0.conjugate() + )/2 + f_fft[ikyn, ikx_kx0] = ((f_kp_kx0+f_kn_kx0.conjugate() + )/2).conjugate() + + f_kp_kxM = f_fft[ikyp, ikx_kxM] + f_kn_kxM = f_fft[ikyn, ikx_kxM] + f_fft[ikyp, ikx_kxM] = (f_kp_kxM+f_kn_kxM.conjugate() + )/2 + f_fft[ikyn, ikx_kxM] = ((f_kp_kxM+f_kn_kxM.conjugate() + )/2).conjugate() + else: + nky_seq = self.shapeK_seq[0] + iky_kyM = nky_seq // 2 + + if self.rank == 0: + # first, some values have to be real + f_fft[0, 0] = f_fft[0, 0].real + f_fft[0, iky_kyM] = f_fft[0, iky_kyM].real + # second, there are relations between some values + for ikyp in xrange(1, iky_kyM): + ikyn = nky_seq - ikyp + f_kp_kx0 = f_fft[0, ikyp] + f_kn_kx0 = f_fft[0, ikyn] + f_fft[0, ikyp] = (f_kp_kx0+f_kn_kx0.conjugate() + )/2 + f_fft[0, ikyn] = ((f_kp_kx0+f_kn_kx0.conjugate() + )/2).conjugate() + + if self.rank == self.nb_proc-1: + ikx_kxM = f_fft.shape[0]-1 + # first, some values have to be real + f_fft[ikx_kxM, 0] = f_fft[ikx_kxM, 0].real + f_fft[ikx_kxM, iky_kyM] = f_fft[ikx_kxM, iky_kyM].real + # second, there are relations between some values + for ikyp in xrange(1, iky_kyM): + ikyn = nky_seq - ikyp + f_kp_kxM = f_fft[ikx_kxM, ikyp] + f_kn_kxM = f_fft[ikx_kxM, ikyn] + f_fft[ikx_kxM, ikyp] = ( + f_kp_kxM+f_kn_kxM.conjugate())/2 + f_fft[ikx_kxM, ikyn] = ( + (f_kp_kxM+f_kn_kxM.conjugate())/2).conjugate() diff --git a/fluidsim/operators/fft/Sources_fftw2dmpicy/fftw3.pxd b/fluidsim/operators/fft/Sources_fftw2dmpicy/fftw3.pxd new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC9Tb3VyY2VzX2ZmdHcyZG1waWN5L2ZmdHczLnB4ZA== --- /dev/null +++ b/fluidsim/operators/fft/Sources_fftw2dmpicy/fftw3.pxd @@ -0,0 +1,54 @@ + + + + +cdef extern from "complex.h": + pass + +cdef extern from "stddef.h": + ctypedef void ptrdiff_t + +cdef extern from "fftw3.h": + ctypedef struct fftw_plan_s: + pass + ctypedef fftw_plan_s *fftw_plan + ctypedef struct fftw_iodim: + int n + int ins "is" + int ous "os" + + fftw_plan fftw_plan_dft_r2c_2d(int n0, + int n1, + double* in_, + complex* out_, + unsigned flags) + + fftw_plan fftw_plan_dft_c2r_2d(int n0, + int n1, + complex* in_, + double* out_, + unsigned flags) + + double* fftw_alloc_real(size_t n) + complex* fftw_alloc_complex(size_t n) + void fftw_execute(fftw_plan plan) + void fftw_destroy_plan(fftw_plan plan) + void fftw_free(void *mem) + + +cdef enum: + FFTW_FORWARD = -1 + FFTW_BACKWARD = +1 + FFTW_MEASURE = 0 + FFTW_DESTROY_INPUT = (1 << 0) + FFTW_UNALIGNED = (1 << 1) + FFTW_CONSERVE_MEMORY = (1 << 2) + FFTW_EXHAUSTIVE = (1 << 3) # /* NO_EXHAUSTIVE is default */ + FFTW_PRESERVE_INPUT = (1 << 4) # /* cancels FFTW_DESTROY_INPUT */ + FFTW_PATIENT = (1 << 5) # /* IMPATIENT is default */ + FFTW_ESTIMATE = (1 << 6) + FFTW_MPI_TRANSPOSED_IN = (1U << 29) + FFTW_MPI_TRANSPOSED_OUT = (1U << 30) + FFTW_MPI_DEFAULT_BLOCK = 0 + + diff --git a/fluidsim/operators/fft/Sources_fftw2dmpicy/fftw3mpi.pxd b/fluidsim/operators/fft/Sources_fftw2dmpicy/fftw3mpi.pxd new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC9Tb3VyY2VzX2ZmdHcyZG1waWN5L2ZmdHczbXBpLnB4ZA== --- /dev/null +++ b/fluidsim/operators/fft/Sources_fftw2dmpicy/fftw3mpi.pxd @@ -0,0 +1,35 @@ + + + +cdef extern from "fftw3.h": + ctypedef struct fftw_plan_s: + pass + ctypedef fftw_plan_s *fftw_plan + + +from mpi4py cimport MPI + +cdef extern from "fftw3-mpi.h": + size_t fftw_mpi_local_size_2d(size_t n0, size_t n1, MPI.MPI_Comm comm, + ptrdiff_t *local_n0, ptrdiff_t *local_0_start) + + size_t fftw_mpi_local_size_2d_transposed( + size_t n0, size_t n1, MPI.MPI_Comm comm, + ptrdiff_t *local_n0, ptrdiff_t *local_0_start, + ptrdiff_t *local_n1, ptrdiff_t *local_1_start) + + fftw_plan fftw_mpi_plan_dft_r2c_2d(int n0, + int n1, + double *in_, + complex *out, + MPI.MPI_Comm comm, + unsigned flags) + fftw_plan fftw_mpi_plan_dft_c2r_2d(int n0, + int n1, + complex *in_, + double *out, + MPI.MPI_Comm comm, + unsigned flags) + + void fftw_mpi_init() + void fftw_mpi_cleanup() diff --git a/fluidsim/operators/fft/__init__.py b/fluidsim/operators/fft/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC9fX2luaXRfXy5weQ== --- /dev/null +++ b/fluidsim/operators/fft/__init__.py @@ -0,0 +1,15 @@ +"""Fast Fourier transforms (:mod:`fluidsim.operators.fft`) +================================================================ + +.. currentmodule:: fluidsim.operators.fft + +Provides + +.. autosummary:: + :toctree: + + easypyfft + fftw2dmpiccy + fftw2dmpicy + +""" diff --git a/fluidsim/operators/fft/easypyfft.py b/fluidsim/operators/fft/easypyfft.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC9lYXN5cHlmZnQucHk= --- /dev/null +++ b/fluidsim/operators/fft/easypyfft.py @@ -0,0 +1,263 @@ +"""Fast Fourier transforms (:mod:`fluidsim.operators.fft.easypyfft`) +========================================================================== + +.. currentmodule:: fluidsim.operators.fft.easypyfft + +Provides classes for performing fft in 1, 2, and 3 dimensions: + +.. autoclass:: fftp2D + :members: + +.. autoclass:: FFTW2DReal2Complex + :members: + +.. autoclass:: FFTW3DReal2Complex + :members: + +.. autoclass:: FFTW1D + :members: + +.. autoclass:: FFTW1DReal2Complex + :members: + +""" + +from __future__ import division, print_function + +import numpy as np + +import scipy.fftpack as fftp + +nthreads = 4 + + +class fftp2D: + """ A class to use fftp """ + def __init__(self, nx, ny): + if nx % 2 != 0 or ny % 2 != 0: + raise ValueError('nx and ny should be even') + self.nx = nx + self.ny = ny + self.shapeX = [ny, nx] + self.nkx = int(float(nx)/2+1) + self.shapeK = [ny, self.nkx] + self.coef_norm = nx*ny + + self.fft2D = self.fftp2D + self.ifft2D = self.ifftp2D + + def fftp2D(self, ff): + if not (isinstance(ff[0, 0], float)): + print('Warning: not array of floats') + big_ff_fft = fftp.fft2(ff)/self.coef_norm + small_ff_fft = big_ff_fft[:, 0:self.nkx] + return small_ff_fft + + def ifftp2D(self, small_ff_fft, ARG_IS_COMPLEX=False): + if not (isinstance(small_ff_fft[0, 0], complex)): + print('Warning: not array of complexes') + print('small_ff_fft\n', small_ff_fft) + big_ff_fft = np.empty(self.shapeX, dtype=np.complex128) + big_ff_fft[:, 0:self.nkx] = small_ff_fft + for iky in range(self.ny): + big_ff_fft[iky, self.nkx:] = \ + small_ff_fft[-iky, self.nkx-2:0:-1].conj() + + print('big_ff_fft final\n', big_ff_fft) + result_ifft = fftp.ifft2(big_ff_fft*self.coef_norm) + if np.max(np.imag(result_ifft)) > 10**(-8): + print ('ifft2: imaginary part of ifft not equal to zero,', + np.max(np.imag(result_ifft))) + return np.real(result_ifft) + + +class FFTW2DReal2Complex: + """ A class to use fftw """ + def __init__(self, nx, ny): + try: + import pyfftw + except ImportError as err: + raise ImportError( + "ImportError {0}. Instead fftpack can be used (?)", err) + if nx % 2 != 0 or ny % 2 != 0: + raise ValueError('nx and ny should be even') + shapeX = [ny, nx] + shapeK = [ny, nx//2 + 1] + + self.shapeX = shapeX + self.shapeK = shapeK + + self.arrayX = pyfftw.n_byte_align_empty(shapeX, 16, 'float64') + self.arrayK = pyfftw.n_byte_align_empty(shapeK, 16, 'complex128') + + self.fftplan = pyfftw.FFTW(input_array=self.arrayX, + output_array=self.arrayK, + axes=(0, 1), + direction='FFTW_FORWARD', + threads=nthreads) + self.ifftplan = pyfftw.FFTW(input_array=self.arrayK, + output_array=self.arrayX, + axes=(0, 1), + direction='FFTW_BACKWARD', + threads=nthreads) + + self.coef_norm = nx*ny + + def fft2d(self, ff): + self.arrayX[:] = ff + self.fftplan(normalise_idft=False) + return self.arrayK/self.coef_norm + + def ifft2d(self, ff_fft): + self.arrayK[:] = ff_fft + self.ifftplan(normalise_idft=False) + return self.arrayX.copy() + + def compute_energy_from_Fourier(self, ff_fft): + return (np.sum(abs(ff_fft[:, 0])**2 + abs(ff_fft[:, -1])**2) + + 2*np.sum(abs(ff_fft[:, 1:-1])**2))/2 + + def compute_energy_from_spatial(self, ff): + return np.mean(abs(ff)**2)/2 + + def project_fft_on_realX(self, ff_fft): + return self.fft2d(self.ifft2d(ff_fft)) + + +class FFTW3DReal2Complex: + """ A class to use fftw """ + def __init__(self, nx, ny, nz): + try: + import pyfftw + except ImportError as err: + raise ImportError( + "ImportError {0}. Instead fftpack can be used (?)", err) + if nx % 2 != 0 or ny % 2 != 0 or nz % 2 != 0: + raise ValueError('nx, ny and nz should be even') + shapeX = [nz, ny, nx] + shapeK = [nz, ny, nx//2 + 1] + + self.shapeX = shapeX + self.shapeK = shapeK + + self.arrayX = pyfftw.n_byte_align_empty(shapeX, 16, 'float64') + self.arrayK = pyfftw.n_byte_align_empty(shapeK, 16, 'complex128') + + self.fftplan = pyfftw.FFTW(input_array=self.arrayX, + output_array=self.arrayK, + axes=(0, 1, 2), + direction='FFTW_FORWARD', + threads=nthreads) + self.ifftplan = pyfftw.FFTW(input_array=self.arrayK, + output_array=self.arrayX, + axes=(0, 1, 2), + direction='FFTW_BACKWARD', + threads=nthreads) + + self.coef_norm = nx*ny*nz + + def fft3d(self, ff): + self.arrayX[:] = ff + self.fftplan(normalise_idft=False) + return self.arrayK/self.coef_norm + + def ifft3d(self, ff_fft): + self.arrayK[:] = ff_fft + self.ifftplan(normalise_idft=False) + return self.arrayX.copy() + + def compute_energy_from_Fourier(self, ff_fft): + return (np.sum(abs(ff_fft[:, :, 0])**2 + abs(ff_fft[:, :, -1])**2) + + 2*np.sum(abs(ff_fft[:, :, 1:-1])**2))/2 + + def compute_energy_from_spatial(self, ff): + return np.mean(abs(ff)**2)/2 + + def project_fft_on_realX(self, ff_fft): + return self.fft2d(self.ifft2d(ff_fft)) + + +class FFTW1D: + """ A class to use fftw 1D """ + def __init__(self, n): + try: + import pyfftw + except ImportError as err: + raise ImportError("ImportError. Instead fftpack?", err) + + if n % 2 != 0: + raise ValueError('n should be even') + shapeX = [n] + shapeK = [n] + self.shapeX = shapeX + self.shapeK = shapeK + self.arrayX = pyfftw.n_byte_align_empty(shapeX, 16, 'complex128') + self.arrayK = pyfftw.n_byte_align_empty(shapeK, 16, 'complex128') + self.fftplan = pyfftw.FFTW(input_array=self.arrayX, + output_array=self.arrayK, + axes=(-1,), + direction='FFTW_FORWARD', threads=nthreads) + self.ifftplan = pyfftw.FFTW(input_array=self.arrayK, + output_array=self.arrayX, + axes=(-1,), + direction='FFTW_BACKWARD', + threads=nthreads) + + self.coef_norm = n + + def fft(self, ff): + self.arrayX[:] = ff + self.fftplan() + return self.arrayK/self.coef_norm + + def ifft(self, ff_fft): + self.arrayK[:] = ff_fft + self.ifftplan() + return self.arrayX.copy() + + +class FFTW1DReal2Complex: + """ A class to use fftw 1D """ + def __init__(self, n): + try: + import pyfftw + except ImportError as err: + raise ImportError("ImportError. Instead fftpack?", err) + + if n % 2 != 0: + raise ValueError('n should be even') + shapeX = [n] + shapeK = [n//2+1] + self.shapeX = shapeX + self.shapeK = shapeK + self.arrayX = pyfftw.n_byte_align_empty(shapeX, 16, 'float64') + self.arrayK = pyfftw.n_byte_align_empty(shapeK, 16, 'complex128') + self.fftplan = pyfftw.FFTW(input_array=self.arrayX, + output_array=self.arrayK, + axes=(-1,), + direction='FFTW_FORWARD', threads=nthreads) + self.ifftplan = pyfftw.FFTW(input_array=self.arrayK, + output_array=self.arrayX, + axes=(-1,), + direction='FFTW_BACKWARD', + threads=nthreads) + + self.coef_norm = n + + def fft(self, ff): + self.arrayX[:] = ff + self.fftplan(normalise_idft=False) + return self.arrayK/self.coef_norm + + def ifft(self, ff_fft): + self.arrayK[:] = ff_fft + self.ifftplan(normalise_idft=False) + return self.arrayX.copy() + + def compute_energy_from_Fourier(self, ff_fft): + return (abs(ff_fft[0])**2 + + 2*np.sum(abs(ff_fft[1:-1])**2) + + abs(ff_fft[-1])**2)/2 + + def compute_energy_from_spatial(self, ff): + return np.mean(abs(ff)**2)/2 diff --git a/fluidsim/operators/fft/profile_fft.ods b/fluidsim/operators/fft/profile_fft.ods new file mode 100644 index 0000000000000000000000000000000000000000..258ce4eb5da5d6578696f3beedf30da104a1209d GIT binary patch literal 12484 zc$}451y~%*vc}!rEf6fY1QvG)!69hy#bJS6ToRl>a1D^)u0evkOMu|+65L(#NY2T< z$+_p;@4nfucc*LW|Ep?xr@MN(mE~dK@SvcOprD4WL&O7Y_(NEsprF2&2N9I5r7aNR zW)C#7x3{r0F@jiv?AV;`Oj$ujU`sG7$R22C3NmrB1=>Ma!S;?oBU7*i5C~EJ!HI-~ z^n(-Xmjm^on1Jjc-vO?+HhX%ypjlo#@5NGfb;BtN)0gB1@mVcp=tPleo$g|bC^{K7 z8FgV<CLYb+L9uk9;Xrx0uXV)+dXa)bi?uO=*zR}kaUG3_&xw0SNZYq&r7?5G2%;g* zCZ4X}68Jgw7w;kOrLd6?X$O3zeAVm_wELD9DeQA?RErjfP?HonJCKsI7+P)G{N&<S zE7y|7^67C36OBB{tIZNRob19i8(1pRG+e(_3YF8AfP?T@W;s!H*sb0R_04dWYS~B= zYYFvY(+j8;8Ap59a%`k*;`ZyXCfsFD4t*VP>{|Uur^)Q&7s6^Vtfd;@6JYYBC2{?f zyf90FUB-4Dx6&zUM2fSsvdR)mCZZupZQwQB7{1T6T_zv`#{jcp;>#JkH9MGHrqU@z z&X`BNq0B4K)#&|GG0%mi#V9At(BsW}J5o(<%TPKCd^Pykwc_z7c{{h-@fsN@fi%<E zH6^w;FE~n6*f68n7?%?#di?y>phNunR<z59EJQc8M2%Juv5z%ykR{3x5GI-8nT-i} z?e4bj_g}JiBhTQ(tadz>BbM2k;8f>JR~hw@a`>R4PyE=*z_V4?j&3%E-T}OYRY<q> zHgH*A6|1`<?X4(IGA10G0b|vfz2I9GIdyo}R)=?V_<Pdi4SgJR;<$LBqU^W`(tb-l z$8ur1#iiWZRkMn4k_6KA0{gTwEs5P1(@S?E@mgG+M=<aZKXri@tt%t1aTQhiI+~K( zK24)GbX<>k<X~yo>FmPr5Vu8i&cR&ikw|+H5mm3h(KDs0D0qXUXvQTD%_~9Hv3p*Z zS2PYOB|>GMBXWW3tBlRMz*ysSSH6EX;@`1Dx@sP4zFSRRXFT$iQ5V%69H_?5Ov|W? z(}fq3atPHo1)CvxJqLI)bKuXV$nngb68gH#)x%;oxW<f(zT31iX}9j34C)7DbA+6^ zT+W(X4Q>?9T$tHlwoJ;wWY?%H>0a$rZg8;lbN<#MLSPId!&PUBF%s33vS@;UT~7in zo_WALW!OBlTs91k8wT>`hi7~>rf&nipDs7x>I{-XaKd?O<IMWWdku3~Uwou}yqP$# zp_eA`s2lpUoraAlkh4|J*SKPEa0|u-yP<Vm31`mfwlD^H0SiN^;&O^IMb<w2Jm?w! zyA~cIH7*%08v?RdOGqrnO_>@BnJWr7IPu*UjZx+m$u$D4q3zGhv7AHldvnQEF`nWx zlXb5@3(s>!>l2G<v8UP3$|%Zudy6<0o(#@R(zSaF_`IUb*zY^#-;SM4CvW?4i7DaY zB!txKt>))Be4n)g!|a@bE6x{Il|9(msf8M9wR?FQou_<=*<B2eE5=NxeM9!eBwws= z*w=UFZF9EZglO+|K}}gms|YWCoOV`Amt-=)V1G{qc$81PDog9BGCMT7-y~fhdL8J? z#+Ae}qD&S|EI4c+rSi4*lfzLTbSYX>ye5F`NouPZp3F@|!lzuBb!Gf<tA=emf*kFd zg(g`?J}C*_?!-lXLa1XG*cV8*ici{+)dt2tKOX8a2IQKOn)T@?3oKALyY(*QY4D}7 zUkMLs6<<Ii_8Ap@&u_LOzl@fd)0ux6o{i+J$b%zV*zRUIK^udephyWKuqT{@W&8jq zFw*K>57RzIvG5pdn@?JGh{@iF<&WlQ-3_z%d_{Pg_Lx=YTwS@L<*~sgJ0{4xW--K8 zN>ilSE9QLR-tFS(bC431F-LYE)A_fB0rxkRGLMgRlCq>^8|jhv`5PHl8V#mOm5`Zv zA{$@$kQJG5!i(M><WnCk-g^OhN`0=PBkv>9H&8uJ3O6BuJ2cHD(FCImJ_)^-wHzrd z4PaG67u4N*qLzDtsmZuidZ_)hBMVePfRbUOX7YrbMLiGY8<nTlRh=Ji&c|I*X@h1W z$&|z#1-)c7nMUG-Y4N{Gfllmh4iRj0&oYaLBF<%{t_g7Zs_v4;jI;U$!?%%SG9Q&7 zSZ!leQlpRaQ97fYV#xZX`*IUmNgQju_w)i>Sy^3Q)3m9njouKbj#hV9IkHH*53HWf zi+vV!%<IQ4S#i)zZWpQ|@Q|@O0&^e7cKD}%f+>LFpRv~irCDVadjeoP<p-l`JgjsI zE|Aj-4Ia-lJ);EOy&ar7OaYjzjgZt=c2P*^>zE`=Ol9*p+qFFWUO}gJgO_8AIW*lA zUOGMZ8RaPo*K4oii6+yqmPwtMCy^T}KTu3nT-0sWCEXWP-G+ST@F=SDEW)&0W0rm7 z1vE~hK|g&63FBJX0P&^Q-t4vhjR23RfuO6EvHPO9@rM!~XAQ(?30s!J^c5r`E9=8N z*%D#eA+<rmhYB4dA-vbCDOC>>fUZf%y5STD%MMovZG^~*)6mYNg>b1g?E;U*s_k{n z#lDNVb<?9x7yg+$5VhSmL}htIq~@;In1qN>P?`_p=Rc39C=Ue;akBw}zYnMLTDA|v zDb{_H0X0KQ8FUQew4AQ>M_8NoGH3<%ti7gD96H=MS)VL}5rgK{P`pNfPIdY$4pn&1 z=01h6i=b*#qd-D{3$3{7Ag^~Nl2=5Te{SqL>9_WqRKKV9^XG-SAAsD{4t+5aa)~He z?o7d_Es4ETigveth8*3|eTmD_Jz8`erAUQgE4%>S^xa?w-pRSwvlAgH<9a4xds8d( zw>=1RpA_MRKl4mz4OiNJII8V3*M=j;#L<7D^@So7EH>R8)~s0&f(>}D7&>azDixk_ z<{ma+Bf)2BKQ4xc5cfD3PZBYPK}RyLD_(NB_LlpT!W;5Fa_oxiRus%~9b_74nEr7m z{vnmw95sV(PiH6c6XbU>LIf2%1F!3|%b#r~^fB4a#%t}-AKxI%w`W~}zn#~XNb*08 z?{dz123KsxHkvVeo^ldcz+09)Km0OqnWub+<#dbYmNt(j173->4OfpgY^8w=7u_Bk zMp87qZq2%KIP}A*rypE?jWT+ajdumCQ~c)_^M&P!3kB|<0yk(BWYR^=0}N-|a*)Gg zQ%V4hyXE)RrlJaR^0lfa919{eLWEzPk<nY7b|)C2nK|K1=-6ZGg6E{2nndAi*|*`6 z0vxOwMI*FnzM7*PaqEU~>$dnc7h@*AZC{RF_J?DXL#b`73QH$G->r!ETy4%C20k}D zARj&ynrh^!%<3HN`bMIu=e^iIEw;E*GjT~p{}u^_=dqG{A4Wz7lg~@n4qp=FFF-31 zWx6=3np5wf;e@R)!%bz+=#W@82T8TMItP#NNozT|K+FJvdF!F@!u%8}>VU!c_XTAw zwJqbqd2m_U3@Dl~0`L}xZG}|Xszx~=#O0O1a`SnJE&3kX(3@Q|L00yDOuiuPwgJ&L z%;(5y7t8M#U9d&(jCkWv{fh6#hm~&JyZB4!y*-KQCO0L;22WtPmxT6$EONZP*cnNC zJI^O5^7l>mXnP9Iu&#w7A4beRm_EGTR@Q8O`v7tqn6S&D3NEz6nDteq1o$LgZ`$MX z;z@ONa6MX`L+&<0NK017OPc&Pcm=Ch2%d(F*!Ji&JkrO$Ik=Iy3TKv~m?273IQsTA zvx#XNubP+bX}A|9Waf`(Y_pHyfyI3NL#o}#9%9!9ND~QH%`!J#7N*_SaiTXV@X?h1 z3rEC4lY`5*v|2mx0X?GWXoThCk}?9wdDpv;ReD1t7oT2)5j>bXxR05damTqKtEKDx zj3i$1Sx$frS*DuCeuQC3?mH(w+BfcDb3)tK<K~Vy5CP9@4;7!(OEISjh(TPG2n<Q| zh$g3&sPZx1E(&(uJXrC(tj?R+!Gw4-vPr)U0-C9(^$%UY-r%Y7YV4p1s`WK>&k<lC zYZ9nieYVAHP=hoHD%q3s%hBcs82B=hg=TWs#|~2wayi!Armiaw?bXPh_0yu2lo+UN zygT1}8L-hU%9Jw0_5=#IDcnjT#d83jtlV<GqBL6h8!t^2RwZ030v(su;j7G?`^NG{ zy;X~HzuGrA{asR{lXGe3kgbW41^h>0LnDg|Nu_U?E*LNTwNB6X_xB0T5a(sbanm)S z014r6dT*GQ*!kd!c@<6<Pj%+}a%@X2XY;}*Nl2CRUOGuO%R-|=JmqF&E}nUcO|Oei zU-L%pm?+?0#~6DoYNccpSV-)iRKrpOq5u+RlxG?3@=lG?C*JeIHIz%f6R&+Av0|{- zpk-=c=_6v>gddVfQI9+d4?wVMKCx<R5w3fqY1E*@lhxgq+kX%`9vd_xdQwvUvUFBd z;2d+Df@E>e;x_c_9_9D$xYIv_TN54%s)yibcl>k8ghQO^^%WKh>U;SyWdcHszIVog zA`f3w&Ok@-!=y=whJ%%zh6-qB0y4F<GZ&)KP?u)mqY)8C5d@i;S(*R=Kj%>_-|<ur zP&+W-k4r){PL6hfA2Tk1oslgN41kybew}&&e$@v2F!YDS)yC4!T8PF10<j0MvAMXo zu)1)uf*j4+IQaSb*?wI8scLHSFFkuFN1Gp7rY3Aa8{l^fU^Wg`4z{1V-#z?ao%*i( zt4kmd=wF7utN-Ej4|AOC?A&aB)PHK5JDQr>{9OPKx?F4z9vVT6Sez|^E{|xa{tW+@ z8_r2X^@9KaaWt|6n}Hl{e@tNi#rNPU_>Y+^%Xe3P5dXpQ5H2U1E7%6Y1~N6{bTl<% zeW=g~|7@%8K?w`~2+h*Y5@Km&!(#F<`GtTSg_SLVj*dVoF(>nf6G67W)%gSYqxRqq z%R>-AVNP}qE*5rf77liG4sHN9FMx;pPxSA4f~F>b{{Zj+xYz+)T!L&rFMikh2akTa z@`oQl(+BVD%vnsG9DmpmR#s<cm*wVC;Nn;N(}n*~`5kHEX7Z4L2y^{K`%m#7y5>MT zpyLm#N)NYM>EEr+#mY&mVQ1+|^}F1_-VtO4G=Z>jakATT@Y9MrS=yMg@bj?q{AuOi zgnt<L*Yg90JopHPSej7%kVA}&ZGbE$ASXMB5Dgd2A3_tLjm=LPAIG0UknwkeKgH}c zY{G(Uzco19-#x0r|D2o=Jrs2dCtG7XBTE}F8|3FHtG%81UV^69sBS_RwhAVU<5$Gk z3{fm-{@B-Pl@jRw3!!6B)ZD0zs3cjBkcixo)BVJ;uq;QU-1C^I6x<a=VODI0-W^=^ z4G3}(a=FAL$Avatyh^BAxTwxJg^X>nI_hPmrKR~<<`KP~5)*R>Tp@gRIJDe2h>n8t zf-x~9I?7<f>;+ZI^r@riOV;`4*J(Ri?i{g?DBbqoGA&n!unL)(-D!#2<r2gLHye5n zsl1GzPgUtK;zseu8@v+9T#u%-%EJ5>_vN@ePYYtSHz41Ydu2lX0a43G_APm6bLrLT zuJ^^U5M^qqHqoX}SPY?dvH&h`I#Eci9#Eb)M2%QVcE=4ad-fInaBE}7>RvqR=3sVK zHl00l4=NJgmEF*(HD#EtJBENKd5?~k)>5N0dQ_Tg^da|atiJ2_4f;;!f_ud+e#don z?eyF>FC7vT(9sEBya?fdUn6ag0|FC(jgbs^+Y5~zii-W9hDjme@dJ#x1zzxS_pFVD zSdNpRpy1`zRZvjS+1VMJUfuDr3&+KJ3YH_@V(ChvTB`B8cP+NHuf6soK@nKxm<cBR zeg3jy^!4)^?;7rT7Is!+Vq%Jmiyy!a3E7KBj3A5UPstJ0(kNBsri~|0c|^Q*x8-l< zzFLpsiH2-8c&+#0uSuN7(9>mR3e_W03h63FMD(uFUIH1PQapE%3U8NCDSWD5Xu~UJ z@TvOMfJ;{v(mUP*)ejR5IGy&r@L1I()#x-eH3yfBsu%`Rh>g&bU_R0r<5_7-G;B79 z_jwQp2QfM-x@?VWE+B<3DXNeV&eEJ{AUbA~m20(cQT4%Fidd0wDZ+{p3e84q>7uYW z1=mlWn*9#5*JCY(haT*1RY<Mk-7+*$695>pqC4QVK`{So79p)&Hv|l<rOk`kmWpak zkv{igGzCQ~TA7bw{@C5mZ7VBG<%mCgb~7)9f8)aa=-k=GC@Cok006LSeP{~N#2K*C z)pQeoFL3GR{3#sH<R&!IRsYPjsAO=wP!krDmuP`w%HNxw4)v>acgI{+xs~wrC;%sm z`NMO+)dO$xfvc#K)ebi})Sd86$jiM)Qhjiv6_c)meTxxsJ!@fVW6$shb+sB>$YT@Z zvRO7YZfj?vq&=J*R06`E7Y=gMs|-1%;5o^a%RcR<jcl>Lwg(&E6sWy&Uv9mg)D<R; z@v_VSI)>gYy;qMQr!6SLk$}gzd>4uPO`+{xBGD7UASNdZ$8FUbZm=jfLDmf7e4j+U z=k{i25VfS<<>376EdPG$<yXZT8k4l#Zg3JK;aAvbRTUM7JFodw(D|~MST6m2R6=F< z;>xAPLek{EHs^W7(eN69T&)6ApNg8=!4a+|0pUAsZMD%+P|wI&<65We3=(IDo2R!# zH_Nw)GTGcupU!WN(({64_0;ju5HfReCj;8UY!nGS-pSU{^_u{53hNI(4DSMKJB(0K zgC<KB8{V-j3sNytKUtU_A0MCh@`R{sspAn5@#(%=G-A+1*1LuCbhDNZl0GZnhgOY| z=-cZ|o+9E^Ntq+$v?Tw+q>jL%soJtMxD#}SupT!yl*c3XA?y_#{~~$+AgSyPIHn^V zs6^{n{za2pv1e*(YGKfPXB6APiJLc`7Xw3|Qlvb#BB!-|-jdJY=>G2J$Po-wIoG|g zAt%48n!zf@k9S)0Q($rNqoicV>F8x54e<7_t-TW0OfgEM{bpYvH&bzvnwgnN>^dHZ zwoOVhz}#(+F*%{^HT>b@>Xjl%xTY+RO7h00-y{C$6?|TnX?p_x%2iVqLC+ddA?dp~ z%7qD+9b7-<cT7gcZ7yK<x2}mhjRK3)6mlXnHkKAscAo8*?$3;3V$%!{c8kK=VzXni z$WY?<gM~WkUOn5SBEVZ3ob+ZLGu}Cv%4GJ>JTm8qUY}6ddimB=ByM%gQYEHmJ&{|Q zm-mxp_E{BAO-!ErXZw#;@7@Ihb|Cfk%Ahv|AxSgAiI4bdo12>ng%<~rv#v<+o9vxr zo}8X$RcU;j=!%TmkKj3?1;7*<fvcnhWW2qb4&jD6-#0ei`jv5+7n7DPDQnyNf!ife za&bQ@O({c@qavf^PDao(9hjM#wQ*G{87AQ7k-;lTyjO?uz$sms9Zukrmj~>3Vu;8D zhpx=ktivF6w7=V4GD&UX2tU$0;p9YI8GuzF{&Ze09to=EunRHTm5~DFO`m+dDZtZT zzKj(Zw!5y<;N9C9s697($#RXQ>rL@^wqnIRi}yM8-V}&G>iEpbSU>mTg21Spr`oS+ z9yiGg#1bbyJN32R*4D^DA|4L6pClnJida8;@EO?AnPR+cYeS2ac=>=0V)W6o9CF0L z(TmnxPbjCIRk5K8svSiX!JK~QVVcWtQkITlbivrp-HYn0(|3o%(okOk?gs_szEM(D zRn@6e6!NjQw0t8c8iXlv2|R`*ZXY_W`RsF7nOtKbCf+c7S|%g5=B+MwyyN3zkd<=V zh<m1|m8|?>WHW*iHn)JBSv(*-#_-V#1iO`lgzD<T7q}mUKMPr}t_QF=q&r%_{Q%sz zw$2zGo?x{><)6_lz_JaKA>x|~P2j#&Ce2Cu=124HWA3TO`038@R+@nyChrveliUxj z+CgSYiS(s*OcLJIbCQmsBFFrGM73s9&_x}0t2@gJ&?(Lc(9}$i9)VZ8JU%1I5E8cD zpM^8ze4&|t)6Sg6+4U?;#$iUht!?VAAS?`BnL69j(k#qKKtRBS)GLE5l%cS&FiKm~ zw2^yhjK~wSf`|<y=2ok#`9#2g^ER!r$fjt(^SB`0F5ENbTa%h!57FcTM|#I$E>)`c zC6o-h*}NiG!0G#T0(`pm6Wb#HgioLBW`=L|tS;m|Oo2eUND`!P>d!qJFv#w2FK28G zd2S?E$PGK3{gNqn$aeRDUe!ye5x6*CyJIzS3mLU?ZZrYhfDQ%^q_!4R`TqW=(xEtp zWw5l2+clTB0jTkPbY-SJ_L^Pf->d}YTW$D>N;=AVs7$oGjj)5`>)jG_qE$%`B~0_; z1G`Ec-;@#YwYS|0!rl9BCPg||U8e=~H-4ScvhfWmD6l#U&7$qfCy5v;XHYrW?jG&< zbm{6A=~zlWlm_TYcB(*t<U#46HSy(ozTqOBNml4<X4p}?^&6mxqJ65A8ILL0vzHic zZ#SXMqP0f3MuEt9@4H?m`_0~<zbBYq-p<m^ZOwPTWCBFWjMJwKok@1FMh$DbH$6<b z=yTm&A-{Bd@(rpq@`LrB+FM0@wi|XnEe)MI8QPVYr8P|VyD^hN%Bw@^z)VxlS^=N> zqV?_RX3U(s^#qTLgW0X?<FgZy$KQB{*%`Mzu^ZdoEHL_F`=DSZ_N|sJMJwmT2oc{q zZS@n@w+%!GcDswraZ==FC@0N_fx(|>9VRJY@VRl5+e(W9IGQZaInh`kW0czx9c#5) zUy8?TxO8IUi$Xr746w1SDth?0){y}yqT!FlhtPE-XIA%8A{Xy<*bO=3ZN5nG5F=n_ z-srXz=;X$VZIrkzS#6jn!HjFDH%0aK_xBTE=;jee&3zGHdX}kqesR$qXnJ``pc#nT z;K@$(RzBsi-skKEnis-?I3>lJXA6m23zaMkT5fJ`h8d#OFcqvZNmH*%s54m8o*vW% z>GVFASDS?6v@AhI-pDg$kxi_(>XBsdV3&@w<isR{VR$m)n1xpuR+OLdr88InhOs$5 zE%#P~Cy*x`He~y)8SzkFTrO7_@ry;dC+P$RjgYNq<WWUeS66p;0K0`HBjXDK0>TIy zfz`O)r{P{jgM)*CnG&4h;ybUm$?8@`Ci&>I_%yzLikQYF-oN#PHi<|}t3I44rDP98 zK#<GNUzLyu*^|p+>Ayu#)oQG+%h~9h*_)1MIu(_bMWWW7;SyL6B=T4$j3wewO3qgx zcKifu+X0EdbMtCAF4@VcJvG7(dh&EET@F_tzfly1@`{q9=bIuFZG~n>FTMEVFjF(L zjg4b6j_YcqZ-|LoSuni?hlWR6v-445`ANaS!4<Kw1YynjLRAKOQ{Motv#8pQXnWkr zI|f`c8;+czSQz~uye(bgJG#3kV|djgQ*12E%mT&ZN@P*0VaK0yMZrK=@lS1K=;fVl zJa3`&h$z^QeMF(&FV=!(o<A3<p>8>r#^b_fXZ~`~Uk{eab=`@I!ZxMmBAum@7Qdra z(eB{@u(GmJFWbK|kk`C8TuLLZku6c}5Dgc{-)P?)(k@C`%(bZMocRI?w7d|rTG}es z5ihs6BdZu5#`jTy8S2!7h_%8{wMMgn!GbV+c|%`5Jd&HaSX4o{dm3ANpPG(Vdt|fK zmPutc&tPdpP2EpIUzg(nE{=HUyqWwBh9^8qlLvSa6D&&CdjuuH<S`SW?1qUG5^6P{ zh??vNunW}_QbCJHv8{P@j~<!G#Sd21lpj}V_-n-Eoz3b6649?LL{CH=x)2>Oq?PJt z8X<C>&I>m!uE8974{&UA3mYKv8E6doL(}Cq%qDvG1vFdUO&rXI&qMr~QGiCUqFqM^ zRDSshtt(?vV^+p*rZ4&A+k>&#V5l#gGAFHsyNdV^+&~$aN}MF3t6$GVYH%Yf5W0r} zFDzutH*r(HJO!D?<U(@pXbmbpmWS^O^G7D$TNv5B5j}Jufhj?_ORlTK3c?lL5hR!D zKb4=Odi2%OGAum(y@cRYa)X*+`p{F=258e1ou%Ec$BdFvQYtL4E{OLXXuF-a4Z|@^ z&Lk?g;kOj-gyob)F)1m#<yhHEZL$7^Soehfmas_eyN9?&#;luCPx&b*ZE{NF<C({0 zvD1ZOLx(8m^g<OKK!l6KKBnwb1@GfsEy_wN4_=yqT><YHRO^%+&-ydCGxlzrFF#lv ze=%q~A3=~v7NqQp6MhP2_utxAAkex?tDyYym3U&!*>4_^TVKJoA!<^mywN7$!VmE( z^Lcs39V~1OHy``>iZShy+Uq5KGR>(eI9J<Tr@5nOn&Rfk$s^peUR!L!Vc&b5(kb@f zUX)x|PUrICJ#3x4y39r|qY@V1L~d^G)2&hZ;X@%M`O(h}XJ|$()*f?rU|wN}<mUC) zRR>)a2dfUmC~6<YXHHIM`L@GEVEcu0qZ~ig52fy&3$vr(*^g{h=an$0HDy6%nvHgB zIyyS`D3c`t1}HF}Suwf~9Z#f#)2l?i=EI_+aeC!idO&iUqGy>)_DHAp=k9eG_}RtK z+5D$1m#aQ@2wuX6YXMN{vvQ5G*0ynXxW_%0eudA~8F&SSq(4T9&#k{pYfPT)vc8C5 zMlx@q&)cpX7+`+#Bs{=&-1YVZcyNWWoq1+Ewv<^x_?+oU+WzRJ%fSM)#A1@|yZ4Ea zFO(CV02h3mjvjR{<ZHgroWrlC$`|D4ze`r?ESI+sWhd>*$pPY)>NBmDulvECC)`kK z#rLsLz$hw*HpAS6lOy1863^UiwZ0o4*JxV9A0<MN3J$JeQjTjV=hSq$K!GB=f`@<p z{P}50d2m}`%4`#CX6B7(%dn%<hl(Q=mBLgK+Lc5!heN^XkIL7*dMNQTi-A6m2$NoD z`vEW><95P%)RsT5wY%v+@RyNPU&AgdGhOp_K7XH|AT{VWeR;t5{)vK1?5w)e=Gd6} zcJ0c_pn+?&HpgTgK%mOtwj|3qI$XA=g#4X}nR(FqEwm1yKhbc_c8{y3uM-c=b{&<- zH{9^{J#8Obmv;7W&Foi-^mGzi+{Y(|i0d|865G+V$pju%R%dCv=(kf5vuf*j3$`Zb z>Tkx_h71{4zp=zBXG$|sfg^*2ZJs=-x@c;Em5WPGuK&2a(PzVqYj!si*0|{6)zF5N zll}#^j+)n~Lej9GCpH?#k($1m$v^yANw7p*(s0*IakzjsKR<6*mwaI%>n+hTs)Vc6 z{1ffGgLsYTv^)z9@B8|uro_|%ttZR-8|M=)0;Gm8J|P`OFhVgC8-;m91h^8oxz>)E zXV9G+ZN84hP2{a45oD|rZ0w9rD|Rwz4Yj7Hr17=2w5g>cUah=-&oLVAr}j}b3r0qm z{k5YRYN&iIE1%qA1LEVys{uNeFOO2f2L1GipVY!aUA#$~?j2vo(mMlV1c9;2d|pR) zw&rI06+1Rt4CV!Z&)-MwDup(~Ghkf+=;`Snt*+{R^Ll#1KCLoLFR3CDDCS=FYzCAy zdIr|wHn=<F-uDzuFCKK498QR=%g-}J0*2gbeih5r$9)4Y2YV|nweFx1mm4HQ2dN!N zQjjfWTF}zosdorF3@Lw6XZWJK_GD9rq|DTUV(QAn1OHm2H9fk$t+$tvhh^__wpy2y z^Yy~hMj@mi#ZKJ!gvdY)6A6TLlQ!-(YCk0}iByqSk13o%8~z`R5jHk9H?fV+lSDu@ zV<;ir8`KFRh7FC4KJ;KY4=CAZJD?_QyU)nZvaDcE%8=)sixe2E)&yTD>Jm3$**m^` zRMrZEi5VPSK!w5%A#Cz)tjY3!Nln|++A3y{x;%B{f0jCE=s;zv!;cpootQ^N;^=<L z!^Vb*6po$8{<<Sa{^k6<mxP>!qpB(nMTAV|5I8ITW{~q-Q;*@r_QvT2>t>l&;WHQY zl}||1JNS{6N3V#G4}C@1M-W(`X>2&L1D^2TRe|d!s>BDYM9e{-(L(5_L~q*mBGEro z9IG`fe-M$m=W@M&b=i!*h-b#2j+wORl+M_KnNyIddKF}Q*$n-ConZ8&A9@TI3JOH< z-<ECZABqIX&dk#M&o`=IPBtSG6QIo>uQb_AoE#nh+#&@F^_})R2<A`l@5>Lr(vtlR zt-T}2+z|){8yh*Y{WFs2Z;)m-AR`FyzoAI}&K}6#$=()d=k))y_8s#7_YndD+5C4D z<=>zzZH>%<U^a0}h^>)5`2QK;_xH<AzueBhYhSeQ%kn_T_t)S*7UuV~7nNq&vAh=z zRMs1_Sc*g?1>zJ*ZEPeIj6@dZ)@F40ntGf``V2(qEwTMg{khGODsu}y-pwbR%wN@C zuFc!F=rChN(@<*7H-?YH?58VT85_P-5UGqPa%q1`&zGu(Nk-Yo097)B6FZ`2D$lk# z(i@2Qg0{v_QgzE7=QSjD(-6tCe!z`yRxS*vWJkRI6xKRl{p*x7S+~d0Jw-^sypXW) zr_nEa>X=t4tOZGZW+L1H!54wi@Wk)Nv`R<$%PPLTu$DOXv38%Z-wE2=!BpZ!C{N|p zz@lxkw56aQLx|_yDq`rk(60!Y5Yh)L(7op%LZ(tfE8a`f3388g1fpXuP`sZ~GSY|h zf(C2&&+qU+-M_!K()M2v+P=Va2^4{?(;_Gr*(K97Tf~r^%9#t&Jv_;wVqM6GgoSf_ zED<MK0gH62GNre-kUaLAC*)8N1q<d%3as&GL?qP6LyZ?>1+2=-;w3QTkH{Tn_DtWo zlU@;BnHwdJy^Z3+JH@D_89*o2Fc=PM_?Ey>>7)3W2rfO(97U+xdncU+k*9SL>b^$} z0?&Bue<=0F!~LwK+OwZYQ(;hdN={p08oN3x@F3Vd<eAYh)gfDlVSx%s*jKpIq%wwx zBs#YXTLsEUc-pg*h_81^vGa6Yl<=P?_lRj=+lte1u<;%d3VOQg#iHjrHCkC^<2SF# z_CC8A%<jhRnR**M!qwn%DbnGL@v);NKc4ex0t}9i7J(hO%~Y;IgX07hF6VZ=f3+6n zUQ<0MZHI0d+=IMLL>xlK-A)@&*LdGV(83e&In#@0DUOSV=Zs+q_wCT@%Q_JbV_Fv4 zx)CB#k%UE>Y!86_WGmV`2=%KqUgM5++LRT4S@^3kJDLj4C2*~!+*|F$*JC#?3!3Yz zm>npZwEPc{4y3tKOY)v%FJifJT-Bw=v_{zFLnh~RQPxf|^p((UuD`4x<EWnvVNCnU zMw<E=9N!srB$k-ysO2f~k>c({b46vQiXBE}`5Q>jsbMo(NG0`Tq4t4F#8Vj<4kpj7 zL;O2r0C-|jZf`^$;anr;#^QebwA-A*krVLx%UiMlYFz_4tb;c+-)0=lb+B(|yZz-@ zoP9b{EyDa$iqS2tC1xfYYS>F6cms|K@73lCU*rXE=)69PMq07-HbE1I=FHa$tIjUX z7&nkpXkOGSs+n<ESGCA}ZS%}Y$RkM-rQ6>Pd(^i@w2_{<X0Rlz>4-zZlE#aR=S0kq zJ%Fm{f+l-=#M|*RFySQ>#w*zGU2wos(#sZDC@68V|JJkPKNJNibukt>MQJu$BRfko zAoxc=UKOWc*T#+&aFIfDEbP!<5P~FIJcc_Ai#ABBb(|__0#^<4eI}=&B8Gpkdvxi+ z_R+jyhOC$ogGkO}GI(=a4)1F$sA`Aw=%W{#L5EqRmbQJJ#L6`%L!ei9Q*M~-mvIVN zwY?Ua@gghA;VON)(D0qqtFydHeLPM3T=aMeV?q2$EzTUYmK$kV2Xv)fR(hk*;=pJn zXi{iuqT<&<+-~-^PR=hB&^7RA#N|CC368Ou`vfSU+X?5D%>~QJ5-3-2Qqe|swL;M< zB&u&rT`(%CdlQWC^<GJ=EgovSf)<?Wl#V>fEf~cZx%r-Z#WHnXUKkDL#z|30F3Ql( zh&}chbTu4UUkJhrbbRi1_=tZt!stMOx8~FEqh8I=1mZb>%aNY8#|Vm7tn2S>Q=>_l z>z~!Bs<rur5x)z%I9!-$h;G*JKcoNN6rO5_p0dzTP!@>an*tgJ59*(HF#mr1+|T?I z^|J|o-ED>X8$uoGw;juW1^&7(0OdDe7UHj4v;OMlUv~}s``|+V_a1`3TKhG-Ao>j@ zhW*cLjelkNuKzQcp#P0Um+pVgOa2<ruQX)8k(U1r>F-&|KQsLv<nG@v{X0wfXBLv* zSZE&q_pIfA^+xA!c>bNm{I4vLf5Y<ctmdCtD1T$=`Wu%2$a4PI8w5-L-?N_I|4qO4 si)g>`2+;qY1^pHI>tl@n8<L0lA9_(`d3c2H3YZU{{Rg8hPrldw4+@yODF6Tf diff --git a/fluidsim/operators/fft/test/__init__.py b/fluidsim/operators/fft/test/__init__.py new file mode 100644 diff --git a/fluidsim/operators/fft/test/test_compare.py b/fluidsim/operators/fft/test/test_compare.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC90ZXN0L3Rlc3RfY29tcGFyZS5weQ== --- /dev/null +++ b/fluidsim/operators/fft/test/test_compare.py @@ -0,0 +1,40 @@ + +from __future__ import division, print_function + +import unittest + +import sys + +import numpy as np + +from fluidsim.operators.fft import easypyfft +try: + from fluidsim.operators.fft import fftw2dmpicy + FFTWMPI = True +except ImportError: + FFTWMPI = False + + +@unittest.skipIf(not FFTWMPI, 'fftw2dmpicy fails to be imported.') +@unittest.skipIf(sys.platform.startswith("win"), "Will fail on Windows") +class TestFFT2Dmpi(unittest.TestCase): + + def test_fft(self): + """Should be able to...""" + nx = 8 + ny = 8 + n0, n1 = ny, nx + op = fftw2dmpicy.FFT2Dmpi(n0, n1, TRANSPOSED=False) + op2 = easypyfft.FFTW2DReal2Complex(nx, ny) + + func_fft = (np.random.random(op.shapeK_loc) + + 1.j*np.random.random(op.shapeK_loc)) + + func = op.ifft2d(func_fft) + func2 = op2.ifft2d(func_fft) + + self.assertTrue(np.allclose(func, func2)) + + +if __name__ == '__main__': + unittest.main() diff --git a/fluidsim/operators/fft/test/test_easypyfft.py b/fluidsim/operators/fft/test/test_easypyfft.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC90ZXN0L3Rlc3RfZWFzeXB5ZmZ0LnB5 --- /dev/null +++ b/fluidsim/operators/fft/test/test_easypyfft.py @@ -0,0 +1,143 @@ + +import unittest + +import numpy as np + + +from fluidsim.operators.fft import easypyfft + +# from fluiddyn.io import stdout_redirected + + +class TestFFTW1DReal2Complex(unittest.TestCase): + + def test_fft(self): + """Should be able to...""" + nx = 128 + op = easypyfft.FFTW1DReal2Complex(nx) + + func_fft = np.zeros(op.shapeK, dtype=np.complex128) + func_fft[0] = 1 + + self.compute_and_check(func_fft, op) + + def compute_and_check(self, func_fft, op): + + func = op.ifft(func_fft) + back_fft = op.fft(func) + back = op.ifft(back_fft) + + self.assertTrue(np.allclose(func_fft, back_fft)) + self.assertTrue(np.allclose(func, back)) + + energyX = op.compute_energy_from_spatial(func) + energyK = op.compute_energy_from_Fourier(func_fft) + energyKback = op.compute_energy_from_Fourier(back_fft) + + self.assertAlmostEqual(energyX, energyK) + self.assertAlmostEqual(energyK, energyKback) + + def test_fft_random(self): + """Should be able to...""" + nx = 128 + op = easypyfft.FFTW1DReal2Complex(nx) + + func_fft = (np.random.random(op.shapeK) + + 1.j*np.random.random(op.shapeK)) + func = op.ifft(func_fft) + func_fft = op.fft(func) + + self.compute_and_check(func_fft, op) + + +class TestFFTW2DReal2Complex(unittest.TestCase): + + def test_fft(self): + """Should be able to...""" + nx = 4 + ny = 2 + op = easypyfft.FFTW2DReal2Complex(nx, ny) + + func_fft = np.zeros(op.shapeK, dtype=np.complex128) + func_fft[0, 1] = 1 + + self.compute_and_check(func_fft, op) + + def compute_and_check(self, func_fft, op): + + energyK = op.compute_energy_from_Fourier(func_fft) + + func = op.ifft2d(func_fft) + energyX = op.compute_energy_from_spatial(func) + + back_fft = op.fft2d(func) + energyKback = op.compute_energy_from_Fourier(back_fft) + back = op.ifft2d(back_fft) + + self.assertTrue(np.allclose(func_fft, back_fft)) + self.assertTrue(np.allclose(func, back)) + + self.assertAlmostEqual(energyX, energyK) + self.assertAlmostEqual(energyK, energyKback) + + def test_fft_random(self): + """Should be able to...""" + nx = 64 + ny = 128 + op = easypyfft.FFTW2DReal2Complex(nx, ny) + + func_fft = (np.random.random(op.shapeK) + + 1.j*np.random.random(op.shapeK)) + func = op.ifft2d(func_fft) + func_fft = op.fft2d(func) + + self.compute_and_check(func_fft, op) + + +class TestFFTW3DReal2Complex(unittest.TestCase): + + def test_fft(self): + """Should be able to...""" + nx = 4 + ny = 2 + nz = 8 + op = easypyfft.FFTW3DReal2Complex(nx, ny, nz) + + func_fft = np.zeros(op.shapeK, dtype=np.complex128) + func_fft[0, 0, 1] = 1 + + self.compute_and_check(func_fft, op) + + def compute_and_check(self, func_fft, op): + + energyK = op.compute_energy_from_Fourier(func_fft) + + func = op.ifft3d(func_fft) + energyX = op.compute_energy_from_spatial(func) + + back_fft = op.fft3d(func) + energyKback = op.compute_energy_from_Fourier(back_fft) + back = op.ifft3d(back_fft) + + self.assertTrue(np.allclose(func_fft, back_fft)) + self.assertTrue(np.allclose(func, back)) + + self.assertAlmostEqual(energyX, energyK) + self.assertAlmostEqual(energyK, energyKback) + + def test_fft_random(self): + """Should be able to...""" + nx = 8 + ny = 8 + nz = 32 + op = easypyfft.FFTW3DReal2Complex(nx, ny, nz) + + func_fft = (np.random.random(op.shapeK) + + 1.j*np.random.random(op.shapeK)) + func = op.ifft3d(func_fft) + func_fft = op.fft3d(func) + + self.compute_and_check(func_fft, op) + +if __name__ == '__main__': + unittest.main() diff --git a/fluidsim/operators/fft/test/test_fftw2dmpicy.py b/fluidsim/operators/fft/test/test_fftw2dmpicy.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL2ZmdC90ZXN0L3Rlc3RfZmZ0dzJkbXBpY3kucHk= --- /dev/null +++ b/fluidsim/operators/fft/test/test_fftw2dmpicy.py @@ -0,0 +1,70 @@ + +import unittest +import numpy as np +import sys + +try: + from fluidsim.operators.fft import fftw2dmpicy + FFTWMPI = True +except ImportError: + FFTWMPI = False + + +@unittest.skipIf(not FFTWMPI, 'fftw2dmpicy fails to be imported.') +@unittest.skipIf(sys.platform.startswith("win"), "Will fail on Windows") +class TestFFT2Dmpi(unittest.TestCase): + + def test_fft(self): + """Should be able to...""" + nx = 4 + ny = 2 + n0, n1 = ny, nx + op = fftw2dmpicy.FFT2Dmpi(n0, n1, TRANSPOSED=False) + + func_fft = np.zeros(op.shapeK_loc, dtype=np.complex128) + func_fft[0, 1] = 1 + + self.compute_and_check(func_fft, op) + + def compute_and_check(self, func_fft, op): + + energyK = op.compute_energy_from_Fourier(func_fft) + + func = op.ifft2d(func_fft) + energyX = op.compute_energy_from_spatial(func) + + back_fft = op.fft2d(func) + energyKback = op.compute_energy_from_Fourier(back_fft) + back = op.ifft2d(back_fft) + + # mean_fft = op.get_mean_fft(func_fft) + + self.assertTrue(np.allclose(func_fft, back_fft)) + self.assertTrue(np.allclose(func, back)) + + self.assertAlmostEqual(energyX, energyK) + self.assertAlmostEqual(energyK, energyKback) + + def test_fft_random(self): + """Should be able to...""" + nx = 32 + ny = 64 + op = fftw2dmpicy.FFT2Dmpi(nx, ny, TRANSPOSED=False) + + func_fft = (np.random.random(op.shapeK_loc) + + 1.j*np.random.random(op.shapeK_loc)) + + func = op.ifft2d(func_fft) + func_fft = op.fft2d(func) + + self.compute_and_check(func_fft, op) + + + + + + + + +if __name__ == '__main__': + unittest.main() diff --git a/fluidsim/operators/op_finitediff.py b/fluidsim/operators/op_finitediff.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL29wX2Zpbml0ZWRpZmYucHk= --- /dev/null +++ b/fluidsim/operators/op_finitediff.py @@ -0,0 +1,246 @@ +"""Operators finite differences (:mod:`fluidsim.operators.op_finitediff`) +=============================================================================== + +.. currentmodule:: fluidsim.operators.op_finitediff + +Provides: + +.. autoclass:: OperatorFiniteDiff1DPeriodic + :members: + :private-members: + +.. autoclass:: OperatorFiniteDiff2DPeriodic + :members: + :private-members: + +""" +import numpy as np +import scipy.sparse as sparse + + +class OperatorFiniteDiff1DPeriodic(object): + + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + attribs = {'nx': 48, 'Lx': 8.} + params.set_child('oper', attribs=attribs) + + def __init__(self, params=None): + + if not params.ONLY_COARSE_OPER: + nx = int(params.oper.nx) + else: + nx = 4 + + Lx = float(params.oper.Lx) + + print(nx, Lx) + + self.nx = nx + self.size = nx + self.shape = [nx] + self.shapeX_loc = self.shape + self.Lx = Lx + self.deltax = Lx/nx + dx = self.deltax + + self.xs = np.linspace(0, Lx, nx) + + self.sparse_px = sparse.diags( + diagonals=[-np.ones(nx-1), np.ones(nx-1), -1, 1], + offsets=[-1, 1, nx-1, -(nx-1)]) + self.sparse_px = self.sparse_px/(2*dx) + + self.sparse_pxx = sparse.diags( + diagonals=[np.ones(nx-1), -2*np.ones(nx), np.ones(nx-1), 1, 1], + offsets=[-1, 0, 1, nx-1, -(nx-1)]) + + self.sparse_pxx = self.sparse_pxx/dx**2 + + def px(self, a): + return self.sparse_px.dot(a.flat) + + def pxx(self, a): + return self.sparse_pxx.dot(a.flat) + + def identity(self): + return sparse.identity(self.size) + + def produce_str_describing_oper(self): + """Produce a string describing the operator.""" + if (self.Lx/np.pi).is_integer(): + str_Lx = repr(int(self.Lx/np.pi)) + 'pi' + else: + str_Lx = '{:.3f}'.format(self.Lx).rstrip('0') + + return ('L='+str_Lx+'_{}').format(self.nx) + + def produce_long_str_describing_oper(self): + """Produce a string describing the operator.""" + if (self.Lx/np.pi).is_integer(): + str_Lx = repr(int(self.Lx/np.pi)) + 'pi' + else: + str_Lx = '{:.3f}'.format(self.Lx).rstrip('0') + return ( + 'Finite difference operator 1D,\n' + 'nx = {0:6d}\n'.format(self.nx) + + 'Lx = ' + str_Lx + '\n') + + +class OperatorFiniteDiff2DPeriodic(OperatorFiniteDiff1DPeriodic): + + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + + attribs = {'nx': 48, + 'ny': 48, + 'Lx': 8, + 'Ly': 8} + params.set_child('oper', attribs=attribs) + + def __init__(self, params=None): + + Lx = float(params.oper.Lx) + Ly = float(params.oper.Ly) + + if not params.ONLY_COARSE_OPER: + nx = int(params.oper.nx) + ny = int(params.oper.ny) + else: + nx = 4 + ny = 4 + + self.nx = nx + self.ny = ny + self.shape = [ny, nx] + size = nx*ny + self.size = size + self.Lx = Lx + self.Ly = Ly + self.deltax = Lx/nx + self.deltay = Ly/ny + dx = self.deltax + dy = self.deltay + + self.xs = np.linspace(0, Lx, nx) + self.ys = np.linspace(0, Ly, ny) + + def func_i1_mat(i0_mat, iv): + i1 = i0_mat % nx + i0 = i0_mat // nx + if iv == 0: + i1_mat = i0*nx + (i1+1) % nx + elif iv == 1: + i1_mat = i0*nx + (i1-1) % nx + else: + raise ValueError('Shouldn''t be here...') + return i1_mat + + values = np.array([1, -1])/(2*dx) + self.sparse_px = self._create_sparse(values, func_i1_mat) + + def func_i1_mat(i0_mat, iv): + i1 = i0_mat % nx + i0 = i0_mat // nx + if iv == 0: + i1_mat = i0_mat + elif iv == 1: + i1_mat = i0*nx + (i1+1) % nx + elif iv == 2: + i1_mat = i0*nx + (i1-1) % nx + else: + raise ValueError('Shouldn''t be here...') + return i1_mat + + values = np.array([-2, 1, 1])/dx**2 + self.sparse_pxx = self._create_sparse(values, func_i1_mat) + + def func_i1_mat(i0_mat, iv): + i1 = i0_mat % nx + i0 = i0_mat // nx + if iv == 0: + i1_mat = ((i0+1)*nx) % size + i1 + elif iv == 1: + i1_mat = ((i0-1)*nx) % size + i1 + else: + raise ValueError('Shouldn''t be here...') + return i1_mat + + values = np.array([1, -1])/(2*dy) + self.sparse_py = self._create_sparse(values, func_i1_mat) + + def func_i1_mat(i0_mat, iv): + i1 = i0_mat % nx + i0 = i0_mat // nx + if iv == 0: + i1_mat = i0_mat + elif iv == 1: + i1_mat = ((i0+1)*nx) % size + i1 + elif iv == 2: + i1_mat = ((i0-1)*nx) % size + i1 + else: + raise ValueError('Shouldn''t be here...') + return i1_mat + + values = np.array([-2, 1, 1])/dx**2 + self.sparse_pyy = self._create_sparse(values, func_i1_mat) + + def _create_sparse(self, values, func_i1_mat): + size = self.size + nb_values = len(values) + data = np.empty(size*nb_values) + i0s = np.empty(size*nb_values) + i1s = np.empty(size*nb_values) + + for i0_mat in xrange(size): + for iv, v in enumerate(values): + data[nb_values*i0_mat+iv] = v + i0s[nb_values*i0_mat+iv] = i0_mat + i1s[nb_values*i0_mat+iv] = func_i1_mat(i0_mat, iv) + return sparse.coo_matrix( + (data, (i0s, i1s)), shape=(size, size)) + + def py(self, a): + return self.sparse_py.dot(a.flat) + + def pyy(self, a): + return self.sparse_pyy.dot(a.flat) + + def produce_str_describing_oper(self): + """Produce a string describing the operator.""" + if (self.Lx/np.pi).is_integer(): + str_Lx = repr(int(self.Lx/np.pi)) + 'pi' + else: + str_Lx = '{:.3f}'.format(self.Lx).rstrip('0') + if (self.Ly/np.pi).is_integer(): + str_Ly = repr(int(self.Ly/np.pi)) + 'pi' + else: + str_Ly = '{:.3f}'.format(self.Ly).rstrip('0') + return ('L='+str_Lx+'x'+str_Ly+'_{}x{}').format( + self.nx, self.ny) + + def produce_long_str_describing_oper(self): + """Produce a string describing the operator.""" + if (self.Lx/np.pi).is_integer(): + str_Lx = repr(int(self.Lx/np.pi)) + 'pi' + else: + str_Lx = '{:.3f}'.format(self.Lx).rstrip('0') + if (self.Ly/np.pi).is_integer(): + str_Ly = repr(int(self.Ly/np.pi)) + 'pi' + else: + str_Ly = '{:.3f}'.format(self.Ly).rstrip('0') + return ( + 'Finite difference operator 2D,\n' + 'nx = {0:6d} ; ny = {1:6d}\n'.format(self.nx, self.ny) + + 'Lx = ' + str_Lx + ' ; Ly = ' + str_Ly + '\n') + + +if __name__ == '__main__': + nx = 3 + ny = 3 + oper = OperatorFiniteDiff2DPeriodic([ny, nx], [nx/2., ny/2.]) + a = np.arange(nx*ny).reshape([ny, nx]) diff --git a/fluidsim/operators/test/__init__.py b/fluidsim/operators/test/__init__.py new file mode 100644 diff --git a/fluidsim/operators/test/test_operators.py b/fluidsim/operators/test/test_operators.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vb3BlcmF0b3JzL3Rlc3QvdGVzdF9vcGVyYXRvcnMucHk= --- /dev/null +++ b/fluidsim/operators/test/test_operators.py @@ -0,0 +1,161 @@ + +import unittest +import numpy as np +import sys + +import fluiddyn as fld + +from fluiddyn.io import stdout_redirected + +from fluidsim.base.solvers.pseudo_spect import info_solver_ps + +from fluidsim.operators.operators import OperatorsPseudoSpectral2D + + +def create_oper(type_fft='FFTWCY'): + + params = fld.simul.create_params(info_solver_ps) + + nh = 8 + params.oper.nx = nh + params.oper.ny = nh + Lh = 6. + params.oper.Lx = Lh + params.oper.Ly = Lh + + params.oper.type_fft = type_fft + + params.oper.coef_dealiasing = 2./3 + + with stdout_redirected(): + oper = OperatorsPseudoSpectral2D(params=params) + + return oper + + +@unittest.skipIf(sys.platform.startswith("win"), "Will fail on Windows") +class TestOperators(unittest.TestCase): + def test_create(self): + """Should be able to ...""" + oper = create_oper('FFTWCY') + + rot = oper.random_arrayX() + rot_fft = oper.fft2(rot) + rot_fft[0, 0] = 0. + + ux_fft, uy_fft = oper.vecfft_from_rotfft(rot_fft) + rot2_fft = oper.rotfft_from_vecfft(ux_fft, uy_fft) + + self.assertTrue(np.allclose(rot2_fft, rot_fft)) + + oper_py = create_oper('FFTWPY') + + ux_fft, uy_fft = oper_py.vecfft_from_rotfft(rot_fft) + rot2_fft = oper_py.rotfft_from_vecfft(ux_fft, uy_fft) + + self.assertTrue(np.allclose(rot2_fft, rot_fft)) + + px_rot_fft, py_rot_fft = oper.gradfft_from_fft(rot_fft) + px_rot2_fft, py_rot2_fft = oper_py.gradfft_from_fft(rot_fft) + + self.assertTrue(np.allclose(px_rot_fft, px_rot2_fft)) + + # fld.ipydebug() + + def test_tendency(self): + + oper = create_oper('FFTWCY') + rot = oper.random_arrayX() + rot_fft = oper.fft2(rot) + rot_fft[0, 0] = 0. + oper.dealiasing(rot_fft) + + ux_fft, uy_fft = oper.vecfft_from_rotfft(rot_fft) + ux = oper.ifft2(ux_fft) + uy = oper.ifft2(uy_fft) + + px_rot_fft, py_rot_fft = oper.gradfft_from_fft(rot_fft) + px_rot = oper.ifft2(px_rot_fft) + py_rot = oper.ifft2(py_rot_fft) + + Frot = -ux*px_rot - uy*py_rot + Frot_fft = oper.fft2(Frot) + oper.dealiasing(Frot_fft) + + T_rot = np.real(Frot_fft.conj()*rot_fft) + + ratio = (oper.sum_wavenumbers(T_rot) / + oper.sum_wavenumbers(abs(T_rot))) + + self.assertGreater(1e-15, ratio) + + # print ('sum(T_rot) = {0:9.4e} ; ' + # 'sum(abs(T_rot)) = {1:9.4e}').format( + # oper.sum_wavenumbers(T_rot), + # oper.sum_wavenumbers(abs(T_rot))) + + oper2 = create_oper('FFTWPY') + + ux_fftpy, uy_fftpy = oper2.vecfft_from_rotfft(rot_fft) + + self.assertTrue(np.allclose(ux_fft, ux_fftpy)) + self.assertTrue(np.allclose(uy_fft, uy_fftpy)) + + uxpy = oper2.ifft2(ux_fftpy) + uypy = oper2.ifft2(uy_fftpy) + + self.assertTrue(np.allclose(ux_fft, ux_fftpy)) + self.assertTrue(np.allclose(uy_fft, uy_fftpy)) + + self.assertTrue(np.allclose(ux, uxpy)) + self.assertTrue(np.allclose(uy, uypy)) + + px_rot_fftpy, py_rot_fftpy = oper2.gradfft_from_fft(rot_fft) + px_rotpy = oper2.ifft2(px_rot_fftpy) + py_rotpy = oper2.ifft2(py_rot_fftpy) + + Frotpy = -uxpy*px_rotpy - uypy*py_rotpy + Frot_fftpy = oper2.fft2(Frotpy) + oper2.dealiasing(Frot_fftpy) + + T_rotpy = np.real(Frot_fftpy.conj()*rot_fft) + + ratio = (oper2.sum_wavenumbers(T_rotpy) / + oper2.sum_wavenumbers(abs(T_rotpy))) + + # print ('sum(T_rot) = {0:9.4e} ; ' + # 'sum(abs(T_rot)) = {1:9.4e}').format( + # oper2.sum_wavenumbers(T_rotpy), + # oper2.sum_wavenumbers(abs(T_rotpy))) + + self.assertGreater(1e-15, ratio) + + def test_laplacian2(self): + oper = create_oper('FFTWCY') + ff = oper.random_arrayX() + ff_fft = oper.fft2(ff) + ff_fft[0, 0] = 0. + + lap_fft = oper.laplacian2_fft(ff_fft) + ff_fft_back = oper.invlaplacian2_fft(lap_fft) + + self.assertTrue(np.allclose(ff_fft, ff_fft_back)) + + def test_monge_ampere(self): + oper = create_oper('FFTWCY') + a = oper.random_arrayX() + a_fft = oper.fft2(a) + a_fft[0, 0] = 0. + + b = oper.random_arrayX() + b_fft = oper.fft2(b) + b_fft[0, 0] = 0. + + ma_py = oper.monge_ampere_from_fft_python(a_fft, b_fft) + ma_cy = oper.monge_ampere_from_fft(a_fft, b_fft) + + self.assertTrue(np.allclose(ma_py, ma_cy)) + + +if __name__ == '__main__': + unittest.main() diff --git a/fluidsim/solvers/__init__.py b/fluidsim/solvers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9fX2luaXRfXy5weQ== --- /dev/null +++ b/fluidsim/solvers/__init__.py @@ -0,0 +1,16 @@ +"""Particular solvers (:mod:`fluidsim.solvers`) + +.. currentmodule:: fluidsim.solvers + +Provides: + +.. autosummary:: + :toctree: + + ns2d + sw1l + plate2d + ad1d + waves2d + +""" diff --git a/fluidsim/solvers/ad1d/__init__.py b/fluidsim/solvers/ad1d/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9hZDFkL19faW5pdF9fLnB5 --- /dev/null +++ b/fluidsim/solvers/ad1d/__init__.py @@ -0,0 +1,14 @@ +"""Advection-diffusion 1D solvers (:mod:`fluidsim.solvers.ad1d`) +====================================================================== + +.. currentmodule:: fluidsim.solvers.ad1d + +Provides: + +.. autosummary:: + :toctree: + + solver + + +""" diff --git a/fluidsim/solvers/ad1d/init_fields.py b/fluidsim/solvers/ad1d/init_fields.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9hZDFkL2luaXRfZmllbGRzLnB5 --- /dev/null +++ b/fluidsim/solvers/ad1d/init_fields.py @@ -0,0 +1,32 @@ + +"""InitFieldsNS2D""" + +import numpy as np + +from fluidsim.base.init_fields import InitFieldsBase + + +class InitFieldsAD1D(InitFieldsBase): + """Init the fields for the solver AD1D.""" + + implemented_flows = ['GAUSSIAN', 'COS'] + + def __call__(self): + """Init the state (in physical and Fourier space) and time""" + + type_flow_init = self.get_and_check_type_flow_init() + + if type_flow_init == 'GAUSSIAN': + self.init_fields_gaussian() + elif type_flow_init == 'COS': + self.init_fields_cos() + else: + raise ValueError('bad value of params.type_flow_init') + + def init_fields_gaussian(self): + s = np.exp(-(10*(self.oper.xs-self.oper.Lx/2))**2) + self.sim.state.state_phys.data[0] = s + + def init_fields_cos(self): + s = np.cos(2*np.pi*self.oper.xs/self.oper.Lx) + self.sim.state.state_phys.data[0] = s diff --git a/fluidsim/solvers/ad1d/output/__init__.py b/fluidsim/solvers/ad1d/output/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9hZDFkL291dHB1dC9fX2luaXRfXy5weQ== --- /dev/null +++ b/fluidsim/solvers/ad1d/output/__init__.py @@ -0,0 +1,40 @@ + + +import numpy as np + +from fluidsim.base.output import OutputBase + + +class Output(OutputBase): + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + info_solver.classes.Output.set_child('classes') + classes = info_solver.classes.Output.classes + + base_name_mod = 'fluidsim.solvers.ad1d.output' + + classes.set_child( + 'PrintStdOut', + attribs={'module_name': base_name_mod+'.print_stdout', + 'class_name': 'PrintStdOutAD1D'}) + + classes.set_child( + 'PhysFields', + attribs={'module_name': 'fluidsim.base.output.phys_fields', + 'class_name': 'PhysFieldsBase1D'}) + + @staticmethod + def _complete_params_with_default(params, info_solver): + """This static method is used to complete the *params* container. + """ + OutputBase._complete_params_with_default(params, info_solver) + + params.output.phys_fields.field_to_plot = 's' + + def compute_energy(self): + return 0.5*np.mean(self.sim.state.state_phys['s']**2) diff --git a/fluidsim/solvers/ad1d/output/print_stdout.py b/fluidsim/solvers/ad1d/output/print_stdout.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9hZDFkL291dHB1dC9wcmludF9zdGRvdXQucHk= --- /dev/null +++ b/fluidsim/solvers/ad1d/output/print_stdout.py @@ -0,0 +1,9 @@ + +from fluidsim.solvers.ns2d.output.print_stdout import PrintStdOutNS2D + + +class PrintStdOutAD1D(PrintStdOutNS2D): + """Used to print in both the stdout and the stdout.txt file, and also + to print simple info on the current state of the simulation. + + """ diff --git a/fluidsim/solvers/ad1d/solver.py b/fluidsim/solvers/ad1d/solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9hZDFkL3NvbHZlci5weQ== --- /dev/null +++ b/fluidsim/solvers/ad1d/solver.py @@ -0,0 +1,141 @@ +"""AD1D solver (:mod:`fluidsim.solvers.ad1d.solver`) +========================================================= + +.. currentmodule:: fluidsim.solvers.ad1d.solver + +Provides: + +.. autoclass:: Simul + :members: + :private-members: + + +""" + +from fluidsim.base.solvers.base import SimulBase +from fluidsim.operators.setofvariables import SetOfVariables +from fluidsim.base.solvers.finite_diff import InfoSolverFiniteDiff + + +info_solver = InfoSolverFiniteDiff() + +package = 'fluidsim.solvers.ad1d' +info_solver.module_name = package + '.solver' +info_solver.class_name = 'Simul' +info_solver.short_name = 'AD1D' + +classes = info_solver.classes + +classes.State.module_name = package + '.state' +classes.State.class_name = 'StateAD1D' + +classes.InitFields.module_name = package + '.init_fields' +classes.InitFields.class_name = 'InitFieldsAD1D' + +classes.Output.module_name = package + '.output' +classes.Output.class_name = 'Output' + +# classes.Forcing.module_name = package + '.forcing' +# classes.Forcing.class_name = 'ForcingAD1D' + + +info_solver.complete_with_classes() + + +class Simul(SimulBase): + r"""Advection-diffusion solver 1D. + + Notes + ----- + + .. |p| mathmacro:: \partial + + We use a finite difference method with the Crank-Nicolson time + scheme to solve the equation + + .. math:: \p_t s + U \p_x s = D(s), + + where :math:`d(s)` is the dissipation term and :math:`U` is a + constant velocity. + + """ + + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + SimulBase._complete_params_with_default(params) + attribs = {'U': 1.} + params.set_attribs(attribs) + + def __init__(self, params): + # the common initialization with the AD1D info_solver: + super(Simul, self).__init__(params, info_solver) + + def tendencies_nonlin(self, state_phys=None): + """Compute the "nonlinear" tendencies.""" + tendencies = SetOfVariables( + like_this_sov=self.state.state_phys, + name_type_variables='tendencies', value=0.) + + if self.params.FORCING: + tendencies += self.forcing.tendencies + + return tendencies + + def linear_operator(self): + """Compute the linear operator as a matrix.""" + + return (- self.params.U*self.oper.sparse_px + + self.params.nu_2*(self.oper.sparse_pxx)) + + +if __name__ == "__main__": + + import fluiddyn as fld + + params = fld.simul.create_params(info_solver) + + params.U = 1. + + params.short_name_type_run = 'test' + + params.oper.nx = 200 + params.oper.Lx = 1. + + # params.oper.type_fft = 'FFTWPY' + + params.time_stepping.type_time_scheme = 'RK2' + + # delta_x = params.oper.Lx/params.oper.nx + params.nu_2 = 0.01 + + params.time_stepping.t_end = 0.4 + params.time_stepping.USE_CFL = True + # params.time_stepping.deltat0 = 0.1 + + params.init_fields.type_flow_init = 'GAUSSIAN' + + params.output.periods_print.print_stdout = 0.25 + + params.output.periods_save.phys_fields = 0.5 + + params.output.periods_plot.phys_fields = 0. + + params.output.phys_fields.field_to_plot = 's' + + # params.output.spectra.has_to_plot = 1 # False + # params.output.spatial_means.has_to_plot = 1 # False + # params.output.spect_energy_budg.has_to_plot = 1 # False + # params.output.increments.has_to_plot = 1 # False + + sim = Simul(params) + + # sim.output.phys_fields.plot() + sim.time_stepping.start() + + print 'x of s_max: ', sim.oper.xs[sim.state.state_phys.data.argmax()] + + sim.output.phys_fields.plot() + + fld.show() diff --git a/fluidsim/solvers/ad1d/state.py b/fluidsim/solvers/ad1d/state.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9hZDFkL3N0YXRlLnB5 --- /dev/null +++ b/fluidsim/solvers/ad1d/state.py @@ -0,0 +1,54 @@ +"""State for the AD1D solver (:mod:`fluidsim.solvers.ad1d.state`) +======================================================================= +""" + + +from fluidsim.base.state import StateBase + +from fluiddyn.util import mpi + + +class StateAD1D(StateBase): + """Contains the variables corresponding to the state and handles the + access to other fields for the solver AD1D. + + """ + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + info_solver.classes.State.set_attribs({ + 'keys_state_phys': ['s'], + 'keys_computable': [], + 'keys_phys_needed': ['s'], + 'keys_linear_eigenmodes': ['s'] + }) + + def compute(self, key, SAVE_IN_DICT=True, RAISE_ERROR=True): + it = self.sim.time_stepping.it + if (key in self.vars_computed + and it == self.it_computed[key]): + return self.vars_computed[key] + + if key == 'dx_s': + result = self.oper.grad(self.state_phys['s']) + + else: + to_print = 'Do not know how to compute "'+key+'".' + if RAISE_ERROR: + raise ValueError(to_print) + else: + if mpi.rank == 0: + print(to_print + + '\nreturn an array of zeros.') + + result = self.oper.constant_arrayX(value=0.) + + if SAVE_IN_DICT: + self.vars_computed[key] = result + self.it_computed[key] = it + + return result diff --git a/fluidsim/solvers/ns2d/__init__.py b/fluidsim/solvers/ns2d/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL19faW5pdF9fLnB5 --- /dev/null +++ b/fluidsim/solvers/ns2d/__init__.py @@ -0,0 +1,13 @@ +"""Navier-Stokes 2D solvers (:mod:`fluidsim.solvers.ns2d`) +================================================================ + +.. currentmodule:: fluidsim.solvers.ns2d + +Provides: + +.. autosummary:: + :toctree: + + solver + +""" diff --git a/fluidsim/solvers/ns2d/forcing.py b/fluidsim/solvers/ns2d/forcing.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL2ZvcmNpbmcucHk= --- /dev/null +++ b/fluidsim/solvers/ns2d/forcing.py @@ -0,0 +1,32 @@ + + +from fluidsim.base.forcing import ForcingBasePseudoSpectral + +from fluidsim.base.forcing.specific import Proportional + +from fluidsim.base.forcing.specific import \ + TimeCorrelatedRandomPseudoSpectral as Random + + +class ForcingNS2D(ForcingBasePseudoSpectral): + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + ForcingBasePseudoSpectral._complete_info_solver(info_solver) + classes = info_solver.classes.Forcing.classes + + package = 'fluidsim.solvers.ns2d.forcing' + + classes.set_child( + 'Random', + attribs={'module_name': package, + 'class_name': 'Random'}) + + classes.set_child( + 'Proportional', + attribs={'module_name': package, + 'class_name': 'Proportional'}) diff --git a/fluidsim/solvers/ns2d/init_fields.py b/fluidsim/solvers/ns2d/init_fields.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL2luaXRfZmllbGRzLnB5 --- /dev/null +++ b/fluidsim/solvers/ns2d/init_fields.py @@ -0,0 +1,45 @@ + +"""InitFieldsNS2D""" + +from fluiddyn.util import mpi + +from fluidsim.base.init_fields import InitFieldsBase + + + +class InitFieldsNS2D(InitFieldsBase): + """Init the fields for the solver NS2D.""" + + implemented_flows = ['NOISE', 'CONSTANT', 'LOAD_FILE', 'DIPOLE', 'JET'] + + def __call__(self): + """Init the state (in physical and Fourier space) and time""" + sim = self.sim + + type_flow_init = self.get_and_check_type_flow_init() + + if type_flow_init == 'DIPOLE': + rot_fft, ux_fft, uy_fft = self.init_fields_1dipole() + tasks_complete_init = ['Fourier_to_phys'] + elif type_flow_init == 'JET': + rot_fft, ux_fft, uy_fft = self.init_fields_jet() + tasks_complete_init = ['Fourier_to_phys'] + elif type_flow_init == 'NOISE': + rot_fft, ux_fft, uy_fft = self.init_fields_noise() + tasks_complete_init = ['Fourier_to_phys'] + elif type_flow_init == 'LOAD_FILE': + self.get_state_from_file(self.params.init_fields.path_file) + tasks_complete_init = [] + elif type_flow_init == 'CONSTANT': + rot_fft = sim.oper.constant_arrayK(value=0.) + if mpi.rank == 0: + rot_fft[1, 0] = 1. + tasks_complete_init = ['Fourier_to_phys'] + else: + raise ValueError('bad value of params.type_flow_init') + + if 'Fourier_to_phys' in tasks_complete_init: + sim.oper.dealiasing(rot_fft) + sim.state.state_fft['rot_fft'] = rot_fft + + sim.state.statephys_from_statefft() diff --git a/fluidsim/solvers/ns2d/output/__init__.py b/fluidsim/solvers/ns2d/output/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL291dHB1dC9fX2luaXRfXy5weQ== --- /dev/null +++ b/fluidsim/solvers/ns2d/output/__init__.py @@ -0,0 +1,70 @@ + + +import numpy as np + +from fluidsim.base.output import OutputBasePseudoSpectral + + +class Output(OutputBasePseudoSpectral): + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + + OutputBasePseudoSpectral._complete_info_solver(info_solver) + + classes = info_solver.classes.Output.classes + + base_name_mod = 'fluidsim.solvers.ns2d.output' + + classes.PrintStdOut.module_name = base_name_mod + '.print_stdout' + classes.PrintStdOut.class_name = 'PrintStdOutNS2D' + + classes.set_child( + 'Spectra', + attribs={'module_name': base_name_mod + '.spectra', + 'class_name': 'SpectraNS2D'}) + + classes.set_child( + 'spatial_means', + attribs={'module_name': base_name_mod + '.spatial_means', + 'class_name': 'SpatialMeansNS2D'}) + + attribs = { + 'module_name': base_name_mod + '.spect_energy_budget', + 'class_name': 'SpectralEnergyBudgetNS2D'} + classes.set_child('spect_energy_budg', attribs=attribs) + + attribs = { + 'module_name': 'fluidsim.base.output.increments', + 'class_name': 'Increments'} + classes.set_child('increments', attribs=attribs) + + @staticmethod + def _complete_params_with_default(params, info_solver): + """This static method is used to complete the *params* container. + """ + OutputBasePseudoSpectral._complete_params_with_default( + params, info_solver) + + params.output.phys_fields.field_to_plot = 'rot' + + def compute_energy_fft(self): + rot_fft = self.sim.state.state_fft['rot_fft'] + ux_fft, uy_fft = self.vecfft_from_rotfft(rot_fft) + return (np.abs(ux_fft)**2+np.abs(uy_fft)**2)/2 + + def compute_enstrophy_fft(self): + rot_fft = self.sim.state.state_fft['rot_fft'] + return np.abs(rot_fft)**2/2 + + def compute_energy(self): + energy_fft = self.compute_energy_fft() + return self.sum_wavenumbers(energy_fft) + + def compute_enstrophy(self): + enstrophy_fft = self.compute_enstrophy_fft() + return self.sum_wavenumbers(enstrophy_fft) diff --git a/fluidsim/solvers/ns2d/output/print_stdout.py b/fluidsim/solvers/ns2d/output/print_stdout.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL291dHB1dC9wcmludF9zdGRvdXQucHk= --- /dev/null +++ b/fluidsim/solvers/ns2d/output/print_stdout.py @@ -0,0 +1,130 @@ + +from __future__ import print_function, division + +from time import time +import numpy as np + +from fluidsim.base.output.print_stdout import PrintStdOutBase + +from fluiddyn.util import mpi + + +class PrintStdOutNS2D(PrintStdOutBase): + """Used to print in both the stdout and the stdout.txt file, and also + to print simple info on the current state of the simulation. + + """ + + def online_print(self): + tsim = self.sim.time_stepping.t + if (tsim-self.t_last_print_info <= self.period_print): + return + + tsim = self.sim.time_stepping.t + itsim = self.sim.time_stepping.it + deltatsim = self.sim.time_stepping.deltat + + energy = self.output.compute_energy() + if mpi.rank == 0: + t_real_word = time() + if self.t_real_word_last == 0.: + duration_left = 0 + else: + if self.params.time_stepping.USE_T_END: + duration_left = int(np.round( + (self.params.time_stepping.t_end - tsim) + * (t_real_word-self.t_real_word_last) + / (tsim - self.t_last_print_info) + )) + else: + duration_left = int(np.round( + (self.params.time_stepping.it_end - itsim) + * (t_real_word-self.t_real_word_last) + )) + to_print = ( + 'it = {0:6d} ; t = {1:9.3f} ; deltat = {2:10.5g}\n'+ + ' energy = {3:9.3e} ; Delta energy = {4:+9.3e}\n'+ + ' estimated remaining duration = {5:6d} s') + to_print = to_print.format( + itsim, tsim, deltatsim, + energy, energy-self.energy_temp, + duration_left) + self.print_stdout(to_print) + self.t_real_word_last = t_real_word + self.energy_temp = energy + self.t_last_print_info = tsim + + def load(self): + dico_results = {'name_solver': self.output.name_solver} + file_means = open(self.output.path_run+'/stdout.txt') + lines = file_means.readlines() + + lines_t = [] + lines_E = [] + for il, line in enumerate(lines): + if line[0:4] == 'it =': + lines_t.append(line) + if line[0:22] == ' energy =': + lines_E.append(line) + + nt = len(lines_t) + if nt > 1: + nt -= 1 + + it = np.zeros(nt, dtype=np.int) + t = np.zeros(nt) + deltat = np.zeros(nt) + + E = np.zeros(nt) + deltaE = np.zeros(nt) + + for il in xrange(nt): + line = lines_t[il] + words = line.split() + it[il] = int(words[2]) + t[il] = float(words[6]) + deltat[il] = float(words[10]) + + line = lines_E[il] + words = line.split() + E[il] = float(words[2]) + deltaE[il] = float(words[7]) + + dico_results['it'] = it + dico_results['t'] = t + dico_results['deltat'] = deltat + dico_results['E'] = E + dico_results['deltaE'] = deltaE + + return dico_results + + def plot(self): + dico_results = self.load() + + t = dico_results['t'] + deltat = dico_results['deltat'] + E = dico_results['E'] + deltaE = dico_results['deltaE'] + + x_left_axe = 0.12 + z_bottom_axe = 0.55 + width_axe = 0.85 + height_axe = 0.4 + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('t') + ax1.set_ylabel('deltat(t)') + + ax1.set_title('info stdout, solver '+self.output.name_solver + + ', nh = {0:5d}'.format(self.nx)) + ax1.hold(True) + ax1.plot(t, deltat, 'k', linewidth=2) + + size_axe[1] = 0.08 + ax2 = fig.add_axes(size_axe) + ax2.set_xlabel('t') + ax2.set_ylabel('E(t), deltaE(t)') + ax2.hold(True) + ax2.plot(t, E, 'k', linewidth=2) + ax2.plot(t, deltaE, 'b', linewidth=2) diff --git a/fluidsim/solvers/ns2d/output/spatial_means.py b/fluidsim/solvers/ns2d/output/spatial_means.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL291dHB1dC9zcGF0aWFsX21lYW5zLnB5 --- /dev/null +++ b/fluidsim/solvers/ns2d/output/spatial_means.py @@ -0,0 +1,257 @@ + +from __future__ import division, print_function + +import os +import numpy as np + + +from fluiddyn.util import mpi + +from fluidsim.base.output.spatial_means import SpatialMeansBase + + +class SpatialMeansNS2D(SpatialMeansBase): + """A :class:`SpatialMean` object handles the saving of .""" + + def save_one_time(self): + tsim = self.sim.time_stepping.t + self.t_last_save = tsim + + energy_fft = self.output.compute_energy_fft() + enstrophy_fft = self.output.compute_enstrophy_fft() + energy = self.sum_wavenumbers(energy_fft) + enstrophy = self.sum_wavenumbers(enstrophy_fft) + + f_d, f_d_hypo = self.sim.compute_freq_diss() + epsK = self.sum_wavenumbers(f_d*2*energy_fft) + epsK_hypo = self.sum_wavenumbers(f_d_hypo*2*energy_fft) + epsZ = self.sum_wavenumbers(f_d*2*enstrophy_fft) + epsZ_hypo = self.sum_wavenumbers(f_d_hypo*2*enstrophy_fft) + + if self.sim.params.FORCING: + deltat = self.sim.time_stepping.deltat + Frot_fft = self.sim.forcing.get_forcing()['rot_fft'] + Fx_fft, Fy_fft = self.vecfft_from_rotfft(Frot_fft) + + rot_fft = self.sim.state.state_fft['rot_fft'] + ux_fft, uy_fft = self.vecfft_from_rotfft(rot_fft) + + PZ1_fft = np.real( + rot_fft.conj()*Frot_fft + + rot_fft*Frot_fft.conj())/2 + PZ2_fft = (abs(Frot_fft)**2)*deltat/2 + + PZ1 = self.sum_wavenumbers(PZ1_fft) + PZ2 = self.sum_wavenumbers(PZ2_fft) + + PK1_fft = np.real( + ux_fft.conj()*Fx_fft + + ux_fft*Fx_fft.conj() + + uy_fft.conj()*Fy_fft + + uy_fft*Fy_fft.conj())/2 + PK2_fft = (abs(Fx_fft)**2+abs(Fy_fft)**2)*deltat/2 + + PK1 = self.sum_wavenumbers(PK1_fft) + PK2 = self.sum_wavenumbers(PK2_fft) + + if mpi.rank == 0: + epsK_tot = epsK+epsK_hypo + + self.file.write( + '####\ntime = {0:7.3f}\n'.format(tsim)) + to_print = ( +'E = {0:11.6e} ; Z = {1:11.6e} \n' +'epsK = {2:11.6e} ; epsK_hypo = {3:11.6e} ; epsK_tot = {4:11.6e} \n' +'epsZ = {5:11.6e} ; epsZ_hypo = {6:11.6e} ; epsZ_tot = {7:11.6e} \n' +).format(energy, enstrophy, + epsK, epsK_hypo, epsK+epsK_hypo, + epsZ, epsZ_hypo, epsZ+epsZ_hypo) + self.file.write(to_print) + + if self.sim.params.FORCING: + PK_tot = PK1+PK2 + to_print = ( +'PK1 = {0:11.6e} ; PK2 = {1:11.6e} ; PK_tot = {2:11.6e} \n' +'PZ1 = {3:11.6e} ; PZ2 = {4:11.6e} ; PZ_tot = {5:11.6e} \n' +).format(PK1, PK2, PK1+PK2, PZ1, PZ2, PZ1+PZ2) + self.file.write(to_print) + + self.file.flush() + os.fsync(self.file.fileno()) + + if self.has_to_plot and mpi.rank == 0: + + self.axe_a.plot(tsim, energy, 'k.') + + self.axe_b.plot(tsim, epsK_tot, 'k.') + if self.sim.params.FORCING: + self.axe_b.plot(tsim, PK_tot, 'm.') + + if (tsim-self.t_last_show >= self.period_show): + self.t_last_show = tsim + fig = self.axe_a.get_figure() + fig.canvas.draw() + + def load(self): + dico_results = {'name_solver': self.output.name_solver} + + file_means = open(self.path_file) + lines = file_means.readlines() + + lines_t = [] + lines_E = [] + lines_PK = [] + lines_PZ = [] + lines_epsK = [] + lines_epsZ = [] + + for il, line in enumerate(lines): + if line.startswith('time ='): + lines_t.append(line) + if line.startswith('E ='): + lines_E.append(line) + if line.startswith('PK1 ='): + lines_PK.append(line) + if line.startswith('PZ1 ='): + lines_PZ.append(line) + if line.startswith('epsK ='): + lines_epsK.append(line) + if line.startswith('epsZ ='): + lines_epsZ.append(line) + + nt = len(lines_t) + if nt > 1: + nt -= 1 + + t = np.empty(nt) + E = np.empty(nt) + Z = np.empty(nt) + PK1 = np.empty(nt) + PK2 = np.empty(nt) + PK_tot = np.empty(nt) + PZ1 = np.empty(nt) + PZ2 = np.empty(nt) + PZ_tot = np.empty(nt) + epsK = np.empty(nt) + epsK_hypo = np.empty(nt) + epsK_tot = np.empty(nt) + epsZ = np.empty(nt) + epsZ_hypo = np.empty(nt) + epsZ_tot = np.empty(nt) + + for il in xrange(nt): + line = lines_t[il] + words = line.split() + t[il] = float(words[2]) + + line = lines_E[il] + words = line.split() + E[il] = float(words[2]) + Z[il] = float(words[6]) + + if self.sim.params.FORCING: + line = lines_PK[il] + words = line.split() + PK1[il] = float(words[2]) + PK2[il] = float(words[6]) + PK_tot[il] = float(words[10]) + + line = lines_PZ[il] + words = line.split() + PZ1[il] = float(words[2]) + PZ2[il] = float(words[6]) + PZ_tot[il] = float(words[10]) + + line = lines_epsK[il] + words = line.split() + epsK[il] = float(words[2]) + epsK_hypo[il] = float(words[6]) + epsK_tot[il] = float(words[10]) + + line = lines_epsZ[il] + words = line.split() + epsZ[il] = float(words[2]) + epsZ_hypo[il] = float(words[6]) + epsZ_tot[il] = float(words[10]) + + dico_results['t'] = t + dico_results['E'] = E + dico_results['Z'] = Z + + dico_results['PK1'] = PK1 + dico_results['PK2'] = PK2 + dico_results['PK_tot'] = PK_tot + + dico_results['PZ1'] = PZ1 + dico_results['PZ2'] = PZ2 + dico_results['PZ_tot'] = PZ_tot + + dico_results['epsK'] = epsK + dico_results['epsK_hypo'] = epsK_hypo + dico_results['epsK_tot'] = epsK_tot + + dico_results['epsZ'] = epsZ + dico_results['epsZ_hypo'] = epsZ_hypo + dico_results['epsZ_tot'] = epsZ_tot + return dico_results + + def plot(self): + dico_results = self.load() + + t = dico_results['t'] + E = dico_results['E'] + Z = dico_results['Z'] + + PK_tot = dico_results['PK_tot'] + PZ_tot = dico_results['PZ_tot'] + + epsK = dico_results['epsK'] + epsK_hypo = dico_results['epsK_hypo'] + epsK_tot = dico_results['epsK_tot'] + + epsZ = dico_results['epsZ'] + epsZ_hypo = dico_results['epsZ_hypo'] + epsZ_tot = dico_results['epsZ_tot'] + + width_axe = 0.85 + height_axe = 0.4 + x_left_axe = 0.12 + z_bottom_axe = 0.55 + + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('t') + ax1.set_ylabel('E(t)') + ax1.hold(True) + ax1.plot(t, E, 'k', linewidth=2) + + z_bottom_axe = 0.08 + size_axe[1] = z_bottom_axe + ax2 = fig.add_axes(size_axe) + ax2.set_xlabel('t') + ax2.set_ylabel('Z(t)') + ax2.hold(True) + ax2.plot(t, Z, 'k', linewidth=2) + + z_bottom_axe = 0.55 + size_axe[1] = z_bottom_axe + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('t') + ax1.set_ylabel('P_E(t), epsK(t)') + ax1.hold(True) + ax1.plot(t, PK_tot, 'c', linewidth=2) + ax1.plot(t, epsK, 'r', linewidth=2) + ax1.plot(t, epsK_hypo, 'g', linewidth=2) + ax1.plot(t, epsK_tot, 'k', linewidth=2) + + z_bottom_axe = 0.08 + size_axe[1] = z_bottom_axe + ax2 = fig.add_axes(size_axe) + ax2.set_xlabel('t') + ax2.set_ylabel('P_Z(t), epsZ(t)') + ax2.hold(True) + ax2.plot(t, PZ_tot, 'c', linewidth=2) + ax2.plot(t, epsZ, 'r', linewidth=2) + ax2.plot(t, epsZ_hypo, 'g', linewidth=2) + ax2.plot(t, epsZ_tot, 'k', linewidth=2) diff --git a/fluidsim/solvers/ns2d/output/spect_energy_budget.py b/fluidsim/solvers/ns2d/output/spect_energy_budget.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL291dHB1dC9zcGVjdF9lbmVyZ3lfYnVkZ2V0LnB5 --- /dev/null +++ b/fluidsim/solvers/ns2d/output/spect_energy_budget.py @@ -0,0 +1,143 @@ +import numpy as np +import h5py + + +from fluidsim.base.output.spect_energy_budget import ( + SpectralEnergyBudgetBase, cumsum_inv) + + +class SpectralEnergyBudgetNS2D(SpectralEnergyBudgetBase): + """Save and plot spectra.""" + + def compute(self): + """compute the spectral energy budget at one time.""" + oper = self.sim.oper + + ux = self.sim.state.state_phys['ux'] + uy = self.sim.state.state_phys['uy'] + + rot_fft = self.sim.state.state_fft['rot_fft'] + ux_fft, uy_fft = oper.vecfft_from_rotfft(rot_fft) + + px_rot_fft, py_rot_fft = oper.gradfft_from_fft(rot_fft) + px_rot = oper.ifft2(px_rot_fft) + py_rot = oper.ifft2(py_rot_fft) + + px_ux_fft, py_ux_fft = oper.gradfft_from_fft(ux_fft) + px_ux = oper.ifft2(px_ux_fft) + py_ux = oper.ifft2(py_ux_fft) + + px_uy_fft, py_uy_fft = oper.gradfft_from_fft(uy_fft) + px_uy = oper.ifft2(px_uy_fft) + py_uy = oper.ifft2(py_uy_fft) + + Frot = -ux*px_rot - uy*(py_rot + self.params.beta) + Frot_fft = oper.fft2(Frot) + oper.dealiasing(Frot_fft) + + Fx = -ux*px_ux - uy*(py_ux) + Fx_fft = oper.fft2(Fx) + oper.dealiasing(Fx_fft) + + Fy = -ux*px_uy - uy*(py_uy) + Fy_fft = oper.fft2(Fy) + oper.dealiasing(Fy_fft) + + transferZ_fft = np.real(rot_fft.conj()*Frot_fft + + rot_fft*Frot_fft.conj())/2. + # print ('sum(transferZ) = {0:9.4e} ; sum(abs(transferZ)) = {1:9.4e}' + # ).format(self.sum_wavenumbers(transferZ_fft), + # self.sum_wavenumbers(abs(transferZ_fft))) + + transferE_fft = np.real(ux_fft.conj()*Fx_fft + + ux_fft*Fx_fft.conj() + + uy_fft.conj()*Fy_fft + + uy_fft*Fy_fft.conj() + )/2. + # print ('sum(transferE) = {0:9.4e} ; sum(abs(transferE)) = {1:9.4e}' + # ).format(self.sum_wavenumbers(transferE_fft), + # self.sum_wavenumbers(abs(transferE_fft))) + + transfer2D_E = self.spectrum2D_from_fft(transferE_fft) + transfer2D_Z = self.spectrum2D_from_fft(transferZ_fft) + + dico_results = { + 'transfer2D_E': transfer2D_E, + 'transfer2D_Z': transfer2D_Z + } + return dico_results + + def _online_plot(self, dico_results): + transfer2D_E = dico_results['transfer2D_E'] + transfer2D_Z = dico_results['transfer2D_Z'] + khE = self.oper.khE + PiE = cumsum_inv(transfer2D_E)*self.oper.deltakh + PiZ = cumsum_inv(transfer2D_Z)*self.oper.deltakh + self.axe_a.plot(khE+khE[1], PiE, 'k') + self.axe_b.plot(khE+khE[1], PiZ, 'g') + + def plot(self, tmin=0, tmax=1000, delta_t=2): + + f = h5py.File(self.path_file, 'r') + dset_times = f['times'] + dset_khE = f['khE'] + khE = dset_khE[...] + khE = khE+khE[1] + + dset_transferE = f['transfer2D_E'] + dset_transferZ = f['transfer2D_Z'] + + # nb_spectra = dset_times.shape[0] + times = dset_times[...] + # nt = len(times) + + delta_t_save = np.mean(times[1:]-times[0:-1]) + delta_i_plot = int(np.round(delta_t/delta_t_save)) + + if delta_i_plot == 0 and delta_t != 0.: + delta_i_plot = 1 + delta_t = delta_i_plot*delta_t_save + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + to_print = 'plot(tmin={0}, tmax={1}, delta_t={2:.2f})'.format( + tmin, tmax, delta_t) + print(to_print) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + print( +'''plot spectral energy budget +tmin = {0:8.6g} ; tmax = {1:8.6g} ; delta_t = {2:8.6g} +imin = {3:8d} ; imax = {4:8d} ; delta_i = {5:8d}'''.format( + tmin_plot, tmax_plot, delta_t, + imin_plot, imax_plot, delta_i_plot)) + + fig, ax1 = self.output.figure_axe() + ax1.set_xlabel('$k_h$') + ax1.set_ylabel('spectra') + ax1.hold(True) + ax1.set_xscale('log') + ax1.set_yscale('linear') + + if delta_t != 0.: + for it in xrange(imin_plot, imax_plot, delta_i_plot): + + transferE = dset_transferE[it] + transferZ = dset_transferZ[it] + + PiE = cumsum_inv(transferE)*self.oper.deltakh + PiZ = cumsum_inv(transferZ)*self.oper.deltakh + + ax1.plot(khE, PiE, 'k', linewidth=1) + ax1.plot(khE, PiZ, 'g', linewidth=1) + + transferE = dset_transferE[imin_plot:imax_plot].mean(0) + transferZ = dset_transferZ[imin_plot:imax_plot].mean(0) + + PiE = cumsum_inv(transferE)*self.oper.deltakh + PiZ = cumsum_inv(transferZ)*self.oper.deltakh + + ax1.plot(khE, PiE, 'r', linewidth=2) + ax1.plot(khE, PiZ, 'm', linewidth=2) diff --git a/fluidsim/solvers/ns2d/output/spectra.py b/fluidsim/solvers/ns2d/output/spectra.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL291dHB1dC9zcGVjdHJhLnB5 --- /dev/null +++ b/fluidsim/solvers/ns2d/output/spectra.py @@ -0,0 +1,156 @@ +import h5py + +import numpy as np + +from fluidsim.base.output.spectra import Spectra + + +class SpectraNS2D(Spectra): + """Save and plot spectra.""" + + def compute(self): + """compute the values at one time.""" + energy_fft = self.output.compute_energy_fft() + # compute the spectra 1D + spectrum1Dkx_E, spectrum1Dky_E = self.spectra1D_from_fft(energy_fft) + dico_spectra1D = {'spectrum1Dkx_E': spectrum1Dkx_E, + 'spectrum1Dky_E': spectrum1Dky_E} + # compute the spectra 2D + spectrum2D_E = self.spectrum2D_from_fft(energy_fft) + dico_spectra2D = {'spectrum2D_E': spectrum2D_E} + return dico_spectra1D, dico_spectra2D + + def _online_plot(self, dico_spectra1D, dico_spectra2D): + if (self.nx == self.params.oper.ny and + self.params.oper.Lx == self.params.oper.Ly): + spectrum2D = dico_spectra2D['spectrum2D_E'] + khE = self.oper.khE + coef_norm = khE**(3.) + self.axe.loglog(khE, spectrum2D*coef_norm, 'k') + lin_inf, lin_sup = self.axe.get_ylim() + if lin_inf < 10e-6: + lin_inf = 10e-6 + self.axe.set_ylim([lin_inf, lin_sup]) + else: + print('you need to implement the ploting ' + 'of the spectra for this case') + + def plot1D(self, tmin=0, tmax=1000, delta_t=2, + coef_compensate=3): + + f = h5py.File(self.path_file1D, 'r') + dset_times = f['times'] + + dset_kxE = f['kxE'] + # dset_kyE = f['kyE'] + kh = dset_kxE[...] + + dset_spectrum1Dkx = f['spectrum1Dkx_E'] + dset_spectrum1Dky = f['spectrum1Dky_E'] + # nb_spectra = dset_times.shape[0] + times = dset_times[...] + # nt = len(times) + + delta_t_save = np.mean(times[1:]-times[0:-1]) + delta_i_plot = int(np.round(delta_t/delta_t_save)) + delta_t = delta_t_save*delta_i_plot + if delta_i_plot == 0: + delta_i_plot = 1 + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + + print( + 'plot1D(tmin={0}, tmax={1}, delta_t={2:.2f},'.format( + tmin, tmax, delta_t) + + ' coef_compensate={0:.3f})'.format(coef_compensate)) + + print('''plot 1D spectra +tmin = {0:8.6g} ; tmax = {1:8.6g} ; delta_t = {2:8.6g} +imin = {3:8d} ; imax = {4:8d} ; delta_i = {5:8d}'''.format( + tmin_plot, tmax_plot, delta_t, + imin_plot, imax_plot, delta_i_plot)) + + fig, ax1 = self.output.figure_axe() + ax1.set_xlabel('$k_h$') + ax1.set_ylabel('spectra') + ax1.set_title('1D spectra, solver '+self.output.name_solver + + ', nh = {0:5d}'.format(self.nx)) + ax1.hold(True) + ax1.set_xscale('log') + ax1.set_yscale('log') + + coef_norm = kh**(coef_compensate) + if delta_t != 0.: + for it in xrange(imin_plot, imax_plot+1, delta_i_plot): + EK = (dset_spectrum1Dkx[it]+dset_spectrum1Dky[it]) + EK[EK < 10e-16] = 0. + ax1.plot(kh, EK*coef_norm, 'k', linewidth=2) + + EK = (dset_spectrum1Dkx[imin_plot:imax_plot+1] + + dset_spectrum1Dky[imin_plot:imax_plot+1]).mean(0) + + ax1.plot(kh, kh**(-3)*coef_norm, 'k', linewidth=1) + ax1.plot(kh, 0.01*kh**(-5/3)*coef_norm, 'k--', linewidth=1) + + def plot2D(self, tmin=0, tmax=1000, delta_t=2, + coef_compensate=3): + f = h5py.File(self.path_file2D, 'r') + dset_times = f['times'] + # nb_spectra = dset_times.shape[0] + times = dset_times[...] + # nt = len(times) + + kh = f['khE'][...] + + dset_spectrum = f['spectrum2D_E'] + + delta_t_save = np.mean(times[1:]-times[0:-1]) + delta_i_plot = int(np.round(delta_t/delta_t_save)) + if delta_i_plot == 0 and delta_t != 0.: + delta_i_plot = 1 + delta_t = delta_i_plot*delta_t_save + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + + print( + 'plot2D(tmin={0}, tmax={1}, delta_t={2:.2f},'.format( + tmin, tmax, delta_t) + + ' coef_compensate={0:.3f})'.format(coef_compensate)) + + print('''plot 2D spectra +tmin = {0:8.6g} ; tmax = {1:8.6g} ; delta_t = {2:8.6g} +imin = {3:8d} ; imax = {4:8d} ; delta_i = {5:8d}'''.format( +tmin_plot, tmax_plot, delta_t, +imin_plot, imax_plot, delta_i_plot)) + + fig, ax1 = self.output.figure_axe() + ax1.set_xlabel('$k_h$') + ax1.set_ylabel('2D spectra') + ax1.set_title('2D spectra, solver ' + self.output.name_solver + + ', nh = {0:5d}'.format(self.nx)) + ax1.hold(True) + ax1.set_xscale('log') + ax1.set_yscale('log') + + coef_norm = kh**coef_compensate + + if delta_t != 0.: + for it in xrange(imin_plot, imax_plot+1, delta_i_plot): + EK = dset_spectrum[it] + EK[EK < 10e-16] = 0. + ax1.plot(kh, EK*coef_norm, 'k', linewidth=1) + + EK = dset_spectrum[imin_plot:imax_plot+1].mean(0) + EK[EK < 10e-16] = 0. + ax1.plot(kh, EK*coef_norm, 'k', linewidth=2) + + ax1.plot(kh, kh**(-3)*coef_norm, 'k--', linewidth=1) + ax1.plot(kh, 0.01*kh**(-5./3)*coef_norm, 'k-.', linewidth=1) diff --git a/fluidsim/solvers/ns2d/solver.py b/fluidsim/solvers/ns2d/solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL3NvbHZlci5weQ== --- /dev/null +++ b/fluidsim/solvers/ns2d/solver.py @@ -0,0 +1,159 @@ +"""NS2D solver (:mod:`fluidsim.solvers.ns2d.solver`) +========================================================= + +.. autoclass:: Simul + :members: + :private-members: + +""" + +from fluidsim.operators.setofvariables import SetOfVariables + +from fluidsim.base.solvers.pseudo_spect import ( + SimulBasePseudoSpectral, InfoSolverPseudoSpectral) + + +info_solver = InfoSolverPseudoSpectral() + +package = 'fluidsim.solvers.ns2d' +info_solver.module_name = package + '.solver' +info_solver.class_name = 'Simul' +info_solver.short_name = 'NS2D' + +classes = info_solver.classes + +classes.State.module_name = package + '.state' +classes.State.class_name = 'StateNS2D' + +classes.InitFields.module_name = package + '.init_fields' +classes.InitFields.class_name = 'InitFieldsNS2D' + +classes.Output.module_name = package + '.output' +classes.Output.class_name = 'Output' + +classes.Forcing.module_name = package + '.forcing' +classes.Forcing.class_name = 'ForcingNS2D' + + +info_solver.complete_with_classes() + + +class Simul(SimulBasePseudoSpectral): + """Pseudo-spectral solver 2D incompressible Navier-Stokes equations. + + """ + + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + SimulBasePseudoSpectral._complete_params_with_default(params) + attribs = {'beta': 0.} + params.set_attribs(attribs) + + def __init__(self, params): + # the common initialization with the NS2D info_solver: + super(Simul, self).__init__(params, info_solver) + + def tendencies_nonlin(self, state_fft=None): + oper = self.oper + fft2 = oper.fft2 + ifft2 = oper.ifft2 + + if state_fft is None: + rot_fft = self.state.state_fft['rot_fft'] + ux = self.state.state_phys['ux'] + uy = self.state.state_phys['uy'] + else: + rot_fft = state_fft['rot_fft'] + ux_fft, uy_fft = oper.vecfft_from_rotfft(rot_fft) + ux = ifft2(ux_fft) + uy = ifft2(uy_fft) + + px_rot_fft, py_rot_fft = oper.gradfft_from_fft(rot_fft) + px_rot = ifft2(px_rot_fft) + py_rot = ifft2(py_rot_fft) + + if self.params.beta == 0: + Frot = -ux*px_rot - uy*py_rot + else: + Frot = -ux*px_rot - uy*(py_rot + self.params.beta) + + Frot_fft = fft2(Frot) + oper.dealiasing(Frot_fft) + + # T_rot = np.real(Frot_fft.conj()*rot_fft + # + Frot_fft*rot_fft.conj())/2. + # print ('sum(T_rot) = {0:9.4e} ; sum(abs(T_rot)) = {1:9.4e}' + # ).format(self.oper.sum_wavenumbers(T_rot), + # self.oper.sum_wavenumbers(abs(T_rot))) + + tendencies_fft = SetOfVariables( + like_this_sov=self.state.state_fft, + name_type_variables='tendencies_nonlin') + + tendencies_fft['rot_fft'] = Frot_fft + + if self.params.FORCING: + tendencies_fft += self.forcing.get_forcing() + + return tendencies_fft + + +if __name__ == "__main__": + + import numpy as np + + import fluiddyn as fld + + params = fld.simul.create_params(info_solver) + + params.short_name_type_run = 'test' + + nh = 32 + Lh = 2*np.pi + params.oper.nx = nh + params.oper.ny = nh + params.oper.Lx = Lh + params.oper.Ly = Lh + + # params.oper.type_fft = 'FFTWPY' + + delta_x = params.oper.Lx/params.oper.nx + params.nu_8 = 2.*10e-1*params.forcing.forcing_rate**(1./3)*delta_x**8 + + params.time_stepping.t_end = 1. + + params.init_fields.type_flow_init = 'NOISE' + + params.FORCING = True + params.forcing.type = 'Random' + # 'Proportional' + # params.forcing.type_normalize + + # params.output.periods_print.print_stdout = 0.25 + + params.output.periods_save.phys_fields = 0.5 + params.output.periods_save.spectra = 0.5 + params.output.periods_save.spatial_means = 0.05 + params.output.periods_save.spect_energy_budg = 0.5 + params.output.periods_save.increments = 0.5 + + params.output.periods_plot.phys_fields = 0.0 + + params.output.ONLINE_PLOT_OK = True + + # params.output.spectra.HAS_TO_PLOT_SAVED = True + # params.output.spatial_means.HAS_TO_PLOT_SAVED = True + # params.output.spect_energy_budg.HAS_TO_PLOT_SAVED = True + # params.output.increments.HAS_TO_PLOT_SAVED = True + + params.output.phys_fields.field_to_plot = 'rot' + + sim = Simul(params) + + # sim.output.phys_fields.plot() + sim.time_stepping.start() + # sim.output.phys_fields.plot() + + fld.show() diff --git a/fluidsim/solvers/ns2d/state.py b/fluidsim/solvers/ns2d/state.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL3N0YXRlLnB5 --- /dev/null +++ b/fluidsim/solvers/ns2d/state.py @@ -0,0 +1,83 @@ +"""State for the NS2D solver (:mod:`fluidsim.solvers.ns2d.state`) +======================================================================= +""" + + +from fluidsim.base.state import StatePseudoSpectral + +from fluiddyn.util import mpi + + +class StateNS2D(StatePseudoSpectral): + """Contains the variables corresponding to the state and handles the + access to other fields for the solver NS2D. + + """ + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + info_solver.classes.State.set_attribs({ + 'keys_state_fft': ['rot_fft'], + 'keys_state_phys': ['ux', 'uy', 'rot'], + 'keys_computable': [], + 'keys_phys_needed': ['rot'], + 'keys_linear_eigenmodes': ['rot_fft']}) + + def compute(self, key, SAVE_IN_DICT=True, RAISE_ERROR=True): + it = self.sim.time_stepping.it + if (key in self.vars_computed + and it == self.it_computed[key]): + return self.vars_computed[key] + + if key == 'ux_fft': + result = self.oper.fft2(self.state_phys['ux']) + elif key == 'uy_fft': + result = self.oper.fft2(self.state_phys['uy']) + elif key == 'rot_fft': + ux_fft = self.compute('ux_fft') + uy_fft = self.compute('uy_fft') + result = self.oper.rotfft_from_vecfft(ux_fft, uy_fft) + elif key == 'div_fft': + ux_fft = self.compute('ux_fft') + uy_fft = self.compute('uy_fft') + result = self.oper.divfft_from_vecfft(ux_fft, uy_fft) + elif key == 'rot': + rot_fft = self.compute('rot_fft') + result = self.oper.ifft2(rot_fft) + elif key == 'div': + div_fft = self.compute('div_fft') + result = self.oper.ifft2(div_fft) + elif key == 'q': + rot = self.compute('rot') + result = rot + else: + to_print = 'Do not know how to compute "' + key + '".' + if RAISE_ERROR: + raise ValueError(to_print) + else: + if mpi.rank == 0: + print(to_print + + '\nreturn an array of zeros.') + + result = self.oper.constant_arrayX(value=0.) + + if SAVE_IN_DICT: + self.vars_computed[key] = result + self.it_computed[key] = it + + return result + + def statephys_from_statefft(self): + rot_fft = self.state_fft['rot_fft'] + self.state_phys['rot'] = self.oper.ifft2(rot_fft) + ux_fft, uy_fft = self.oper.vecfft_from_rotfft(rot_fft) + self.state_phys['ux'] = self.oper.ifft2(ux_fft) + self.state_phys['uy'] = self.oper.ifft2(uy_fft) + + def statefft_from_statephys(self): + rot = self.state_phys['rot'] + self.state_fft['rot_fft'] = self.oper.fft2(rot) diff --git a/fluidsim/solvers/ns2d/strat/__init__.py b/fluidsim/solvers/ns2d/strat/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL3N0cmF0L19faW5pdF9fLnB5 --- /dev/null +++ b/fluidsim/solvers/ns2d/strat/__init__.py @@ -0,0 +1,14 @@ +"""Stratified Navier-Stokes 2D (:mod:`fluidsim.solvers.ns2d.strat`) +========================================================================= + +.. currentmodule:: fluidsim.solvers.ns2d.strat + +Provides: + +.. autosummary:: + :toctree: + + solver + exact + +""" diff --git a/fluidsim/solvers/ns2d/strat/exact/__init__.py b/fluidsim/solvers/ns2d/strat/exact/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL3N0cmF0L2V4YWN0L19faW5pdF9fLnB5 --- /dev/null +++ b/fluidsim/solvers/ns2d/strat/exact/__init__.py @@ -0,0 +1,13 @@ +"""Stratified Navier-Stokes 2D (:mod:`fluidsim.solvers.ns2d.strat.exact`) +=============================================================================== + +.. currentmodule:: fluidsim.solvers.ns2d.strat.exact + +Provides: + +.. autosummary:: + :toctree: + + solver + +""" diff --git a/fluidsim/solvers/ns2d/strat/exact/solver.py b/fluidsim/solvers/ns2d/strat/exact/solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL3N0cmF0L2V4YWN0L3NvbHZlci5weQ== --- /dev/null +++ b/fluidsim/solvers/ns2d/strat/exact/solver.py @@ -0,0 +1,165 @@ +"""Stratified NS2D solver (:mod:`fluidsim.solvers.ns2d.strat.solver`) +=========================================================================== + +.. autoclass:: Simul + :members: + :private-members: + +""" + +from fluidsim.operators.setofvariables import SetOfVariables + +from fluidsim.base.solvers.pseudo_spect import ( + SimulBasePseudoSpectral, InfoSolverPseudoSpectral) + + +info_solver = InfoSolverPseudoSpectral() + +package = 'fluidsim.solvers.ns2d' +info_solver.module_name = package + '.solver' +info_solver.class_name = 'Simul' +info_solver.short_name = 'NS2D' + +classes = info_solver.classes + +classes.State.module_name = package + '.state' +classes.State.class_name = 'StateNS2D' + +classes.InitFields.module_name = package + '.init_fields' +classes.InitFields.class_name = 'InitFieldsNS2D' + +classes.Output.module_name = package + '.output' +classes.Output.class_name = 'Output' + +# classes.Forcing.module_name = package + '.forcing' +# classes.Forcing.class_name = 'ForcingNS2D' + + +info_solver.complete_with_classes() + + +class Simul(SimulBasePseudoSpectral): + r"""Pseudo-spectral solver strat. 2D incompressible Navier-Stokes equations. + + + + + """ + + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + SimulBasePseudoSpectral._complete_params_with_default(params) + attribs = {'N': 1.} + params.set_attribs(attribs) + + def __init__(self, params): + super(Simul, self).__init__(params, info_solver) + + def tendencies_nonlin(self, state_fft=None): + oper = self.oper + fft2 = oper.fft2 + ifft2 = oper.ifft2 + + if state_fft is None: + rot_fft = self.state.state_fft['rot_fft'] + b_fft = self.state.state_fft['b_fft'] + ux = self.state.state_phys['ux'] + uz = self.state.state_phys['uz'] + else: + rot_fft = state_fft['rot_fft'] + b_fft = state_fft['b_fft'] + ux_fft, uz_fft = oper.vecfft_from_rotfft(rot_fft) + ux = ifft2(ux_fft) + uz = ifft2(uz_fft) + + px_rot_fft, pz_rot_fft = oper.gradfft_from_fft(rot_fft) + px_rot = ifft2(px_rot_fft) + pz_rot = ifft2(pz_rot_fft) + + px_b_fft = oper.pxffft_ftt(b_fft) + + Frot = - ux*px_rot - uz*pz_rot + + Frot_fft = fft2(Frot) + px_b_fft + oper.dealiasing(Frot_fft) + + Fb_fft = self.params.N2*uz_fft + + # T_rot = np.real(Frot_fft.conj()*rot_fft + # + Frot_fft*rot_fft.conj())/2. + # print ('sum(T_rot) = {0:9.4e} ; sum(abs(T_rot)) = {1:9.4e}' + # ).format(self.oper.sum_wavenumbers(T_rot), + # self.oper.sum_wavenumbers(abs(T_rot))) + + tendencies_fft = SetOfVariables( + like_this_sov=self.state.state_fft, + name_type_variables='tendencies_nonlin') + + tendencies_fft['rot_fft'] = Frot_fft + tendencies_fft['b_fft'] = Fb_fft + + if self.params.FORCING: + tendencies_fft += self.forcing.get_forcing() + + return tendencies_fft + + +if __name__ == "__main__": + + import numpy as np + + import fluiddyn as fld + + params = fld.simul.create_params(info_solver) + + params.short_name_type_run = 'test' + + nh = 32 + Lh = 2*np.pi + params.oper.nx = nh + params.oper.ny = nh + params.oper.Lx = Lh + params.oper.Ly = Lh + + # params.oper.type_fft = 'FFTWPY' + + delta_x = params.oper.Lx/params.oper.nx + params.nu_8 = 2.*10e-1*params.forcing.forcing_rate**(1./3)*delta_x**8 + + params.time_stepping.t_end = 1. + + params.init_fields.type_flow_init = 'NOISE' + + params.FORCING = True + params.forcing.type = 'Random' + # 'Proportional' + # params.forcing.type_normalize + + # params.output.periods_print.print_stdout = 0.25 + + params.output.periods_save.phys_fields = 0.5 + params.output.periods_save.spectra = 0.5 + params.output.periods_save.spatial_means = 0.05 + params.output.periods_save.spect_energy_budg = 0.5 + params.output.periods_save.increments = 0.5 + + params.output.periods_plot.phys_fields = 0.0 + + params.output.ONLINE_PLOT_OK = True + + # params.output.spectra.HAS_TO_PLOT_SAVED = True + # params.output.spatial_means.HAS_TO_PLOT_SAVED = True + # params.output.spect_energy_budg.HAS_TO_PLOT_SAVED = True + # params.output.increments.HAS_TO_PLOT_SAVED = True + + params.output.phys_fields.field_to_plot = 'rot' + + sim = Simul(params) + + # sim.output.phys_fields.plot() + sim.time_stepping.start() + # sim.output.phys_fields.plot() + + fld.show() diff --git a/fluidsim/solvers/ns2d/strat/solver.py b/fluidsim/solvers/ns2d/strat/solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL3N0cmF0L3NvbHZlci5weQ== --- /dev/null +++ b/fluidsim/solvers/ns2d/strat/solver.py @@ -0,0 +1,159 @@ +"""NS2D solver (:mod:`fluidsim.solvers.ns2d.solver`) +========================================================= + +.. autoclass:: Simul + :members: + :private-members: + +""" + +from fluidsim.operators.setofvariables import SetOfVariables + +from fluidsim.base.solvers.pseudo_spect import ( + SimulBasePseudoSpectral, InfoSolverPseudoSpectral) + + +info_solver = InfoSolverPseudoSpectral() + +package = 'fluidsim.solvers.ns2d' +info_solver.module_name = package + '.solver' +info_solver.class_name = 'Simul' +info_solver.short_name = 'NS2D' + +classes = info_solver.classes + +classes.State.module_name = package + '.state' +classes.State.class_name = 'StateNS2D' + +classes.InitFields.module_name = package + '.init_fields' +classes.InitFields.class_name = 'InitFieldsNS2D' + +classes.Output.module_name = package + '.output' +classes.Output.class_name = 'Output' + +classes.Forcing.module_name = package + '.forcing' +classes.Forcing.class_name = 'ForcingNS2D' + + +info_solver.complete_with_classes() + + +class Simul(SimulBasePseudoSpectral): + """Pseudo-spectral solver 2D incompressible Navier-Stokes equations. + + """ + + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + SimulBasePseudoSpectral._complete_params_with_default(params) + attribs = {'beta': 0.} + params.set_attribs(attribs) + + def __init__(self, params): + # the common initialization with the NS2D info_solver: + super(Simul, self).__init__(params, info_solver) + + def tendencies_nonlin(self, state_fft=None): + oper = self.oper + fft2 = oper.fft2 + ifft2 = oper.ifft2 + + if state_fft is None: + rot_fft = self.state.state_fft['rot_fft'] + ux = self.state.state_phys['ux'] + uy = self.state.state_phys['uy'] + else: + rot_fft = state_fft['rot_fft'] + ux_fft, uy_fft = oper.vecfft_from_rotfft(rot_fft) + ux = ifft2(ux_fft) + uy = ifft2(uy_fft) + + px_rot_fft, py_rot_fft = oper.gradfft_from_fft(rot_fft) + px_rot = ifft2(px_rot_fft) + py_rot = ifft2(py_rot_fft) + + if self.params.beta == 0: + Frot = -ux*px_rot - uy*py_rot + else: + Frot = -ux*px_rot - uy*(py_rot + self.params.beta) + + Frot_fft = fft2(Frot) + oper.dealiasing(Frot_fft) + + # T_rot = np.real(Frot_fft.conj()*rot_fft + # + Frot_fft*rot_fft.conj())/2. + # print ('sum(T_rot) = {0:9.4e} ; sum(abs(T_rot)) = {1:9.4e}' + # ).format(self.oper.sum_wavenumbers(T_rot), + # self.oper.sum_wavenumbers(abs(T_rot))) + + tendencies_fft = SetOfVariables( + like_this_sov=self.state.state_fft, + name_type_variables='tendencies_nonlin') + + tendencies_fft['rot_fft'] = Frot_fft + + if self.params.FORCING: + tendencies_fft += self.forcing.get_forcing() + + return tendencies_fft + + +if __name__ == "__main__": + + import numpy as np + + import fluiddyn as fld + + params = fld.simul.create_params(info_solver) + + params.short_name_type_run = 'test' + + nh = 32 + Lh = 2*np.pi + params.oper.nx = nh + params.oper.ny = nh + params.oper.Lx = Lh + params.oper.Ly = Lh + + # params.oper.type_fft = 'FFTWPY' + + delta_x = params.oper.Lx/params.oper.nx + params.nu_8 = 2.*10e-1*params.forcing.forcing_rate**(1./3)*delta_x**8 + + params.time_stepping.t_end = 1. + + params.init_fields.type_flow_init = 'NOISE' + + params.FORCING = True + params.forcing.type = 'Random' + # 'Proportional' + # params.forcing.type_normalize + + # params.output.periods_print.print_stdout = 0.25 + + params.output.periods_save.phys_fields = 0.5 + params.output.periods_save.spectra = 0.5 + params.output.periods_save.spatial_means = 0.05 + params.output.periods_save.spect_energy_budg = 0.5 + params.output.periods_save.increments = 0.5 + + params.output.periods_plot.phys_fields = 0.0 + + params.output.ONLINE_PLOT_OK = True + + # params.output.spectra.HAS_TO_PLOT_SAVED = True + # params.output.spatial_means.HAS_TO_PLOT_SAVED = True + # params.output.spect_energy_budg.HAS_TO_PLOT_SAVED = True + # params.output.increments.HAS_TO_PLOT_SAVED = True + + params.output.phys_fields.field_to_plot = 'rot' + + sim = Simul(params) + + # sim.output.phys_fields.plot() + sim.time_stepping.start() + # sim.output.phys_fields.plot() + + fld.show() diff --git a/fluidsim/solvers/ns2d/test/__init__.py b/fluidsim/solvers/ns2d/test/__init__.py new file mode 100644 diff --git a/fluidsim/solvers/ns2d/test/test_solver.py b/fluidsim/solvers/ns2d/test/test_solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9uczJkL3Rlc3QvdGVzdF9zb2x2ZXIucHk= --- /dev/null +++ b/fluidsim/solvers/ns2d/test/test_solver.py @@ -0,0 +1,64 @@ +import unittest +import shutil + +import numpy as np + +import fluiddyn as fld + +from fluiddyn.io import stdout_redirected + + +class TestSolverNS2D(unittest.TestCase): + def test_tendency(self): + + key_solver = 'NS2D' + solver = fld.simul.import_module_solver_from_key(key_solver) + params = fld.simul.create_params(solver) + + params.short_name_type_run = 'test' + + nh = 64 + params.oper.nx = nh + params.oper.ny = nh + Lh = 6. + params.oper.Lx = Lh + params.oper.Ly = Lh + + params.oper.coef_dealiasing = 2./3 + params.nu_8 = 2. + + params.oper.type_fft = 'FFTWPY' + # params.oper.type_fft = 'FFTWCY' + + params.time_stepping.t_end = 0.5 + + params.init_fields.type_flow_init = 'NOISE' + params.output.HAS_TO_SAVE = False + params.FORCING = False + + with stdout_redirected(): + sim = solver.Simul(params) + + rot_fft = sim.state('rot_fft') + + tend = sim.tendencies_nonlin(state_fft=sim.state.state_fft) + Frot_fft = tend['rot_fft'] + + T_rot = np.real(Frot_fft.conj()*rot_fft) + + ratio = (sim.oper.sum_wavenumbers(T_rot) / + sim.oper.sum_wavenumbers(abs(T_rot))) + + self.assertGreater(1e-16, ratio) + + # print ('sum(T_rot) = {0:9.4e} ; ' + # 'sum(abs(T_rot)) = {1:9.4e}').format( + # sim.oper.sum_wavenumbers(T_rot), + # sim.oper.sum_wavenumbers(abs(T_rot))) + + # clean by removing the directory + shutil.rmtree(sim.output.path_run) + + +if __name__ == '__main__': + unittest.main() diff --git a/fluidsim/solvers/plate2d/__init__.py b/fluidsim/solvers/plate2d/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL19faW5pdF9fLnB5 --- /dev/null +++ b/fluidsim/solvers/plate2d/__init__.py @@ -0,0 +1,18 @@ +"""Plate2d solvers (:mod:`fluidsim.solvers.plate2d`) +========================================================== + +.. currentmodule:: fluidsim.solvers.plate2d + +Provides: + +.. autosummary:: + :toctree: + + solver + state + output + init_fields + forcing + diag + +""" diff --git a/fluidsim/solvers/plate2d/diag/__init__.py b/fluidsim/solvers/plate2d/diag/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL2RpYWcvX19pbml0X18ucHk= --- /dev/null +++ b/fluidsim/solvers/plate2d/diag/__init__.py @@ -0,0 +1,13 @@ +"""Plate2d solvers diagonalized (:mod:`fluidsim.solvers.plate2d.diag`) +============================================================================ + +.. currentmodule:: fluidsim.solvers.plate2d.diag + +Provides: + +.. autosummary:: + :toctree: + + solver + +""" diff --git a/fluidsim/solvers/plate2d/diag/solver.py b/fluidsim/solvers/plate2d/diag/solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL2RpYWcvc29sdmVyLnB5 --- /dev/null +++ b/fluidsim/solvers/plate2d/diag/solver.py @@ -0,0 +1,326 @@ +# -*- coding: utf-8 -*- + +"""Plate2d solver diag. (:mod:`fluidsim.solvers.plate2d.diag.solver`) +=========================================================================== + +.. currentmodule:: fluidsim.solvers.plate2d.diag.solver + +Provides: + +.. autoclass:: Simul + :members: + :private-members: + +""" + +from __future__ import print_function + +import numpy as np + +from fluidsim.operators.setofvariables import SetOfVariables +from fluidsim.base.solvers.pseudo_spect import ( + SimulBasePseudoSpectral, InfoSolverPseudoSpectral) + + +info_solver = InfoSolverPseudoSpectral() + +package = 'fluidsim.solvers.plate2d' +info_solver.module_name = package + '.solver' +info_solver.class_name = 'Simul' +info_solver.short_name = 'Plate2D' + +classes = info_solver.classes + +classes.State.module_name = package + '.state' +classes.State.class_name = 'StatePlate2D' + +classes.InitFields.module_name = package + '.init_fields' +classes.InitFields.class_name = 'InitFieldsPlate2D' + +classes.Output.module_name = package + '.output' +classes.Output.class_name = 'Output' + +classes.Forcing.module_name = package + '.forcing' +classes.Forcing.class_name = 'ForcingPlate2D' + + +info_solver.complete_with_classes() + + +class Simul(SimulBasePseudoSpectral): + r"""Pseudo-spectral solver solving the Föppl-von Kármán equations. + + Notes + ----- + + .. |p| mathmacro:: \partial + + This class is dedicated to solve with a pseudo-spectral method the + Föppl-von Kármán equations which describe the dynamics of a rigid + plate (see :class:`fluidsim.solvers.plate2d.solver.Simul`). + + In the Fourier space, the governing equations write: + + .. math:: + \p_t \hat z = \hat w, + + \p_t \hat w = - \Omega(k)^2 \hat z + \widehat{N_w(z)} + \hat F_w + - \gamma_w \hat w, + + where :math:`\Omega(k) = k^4`, :math:`k^2 = |\mathbf{k}|^2` and + :math:`\gamma_w = \nu_\alpha k^{2\alpha}`. For this solver, we + will use variables that diagonalized the linear terms, i.e. that + represent propagative waves (see + :class:`fluidsim.solvers.waves2d.solver.Simul`). Therefore, + all the linear terms can be solved exactly. Applying the equations + of :class:`fluidsim.solvers.waves2d.solver.Simul` with + :math:`\gamma_f = \gamma_z = 0`, we find that the eigenvalues are + :math:`\sigma_\pm = - \gamma_w/2 \pm i \tilde \Omega` with + :math:`\tilde \Omega = \Omega \sqrt{1 - (\gamma_w/(2 \Omega))^2}`, + and the (not normalized) eigenvectors are + + .. math:: + + V_\pm = \begin{pmatrix} 1 \\ \sigma_\pm \end{pmatrix}. + + The state can be represented by a vector :math:`A` that verifies + :math:`X = V A`, where :math:`V` is the base matrix + + .. math:: + + V = \begin{pmatrix} 1 & 1 \\ + \sigma_+ & \sigma_- \end{pmatrix}. + + The inverse base matrix is given by + + .. math:: + + V^{-1} = \frac{i}{2\tilde \Omega} + \begin{pmatrix} + \sigma_- & -1 \\ + -\sigma_+ & 1 \end{pmatrix}, + + which gives more explicitly that + + .. math:: + + A = \begin{pmatrix} \hat a_+ \\ \hat a_- \end{pmatrix} = + \frac{i}{2\tilde \Omega} \begin{pmatrix} \sigma_- \hat z - \hat + w \\ -\sigma_+ \hat z + \hat w \end{pmatrix}. + + The governing equations can then be expressed as + + .. math:: + + \p_t A = L A + N(A), + + with + + + .. math:: + + L = \begin{pmatrix} \sigma_+ & 0 \\ 0 & \sigma_- \end{pmatrix}, + + N = \frac{i}{2\tilde \Omega} \begin{pmatrix} -(\widehat{N_w(z)} + + \hat F_w) \\ \widehat{N_w(z)} + \hat F_w \end{pmatrix}. + + """ + + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + SimulBasePseudoSpectral._complete_params_with_default(params) + attribs = {'beta': 0.} + params.set_attribs(attribs) + + def __init__(self, params): + # the common initialization with the PLATE2D info_solver: + super(Simul, self).__init__(params, info_solver) + + def tendencies_nonlin(self, state_fft=None): + """Compute the "nonlinear" tendencies.""" + oper = self.oper + + if state_fft is None: + w_fft = self.state.state_fft['w_fft'] + z_fft = self.state.state_fft['z_fft'] + else: + w_fft = state_fft['w_fft'] + z_fft = state_fft['z_fft'] + + mamp_zz = oper.monge_ampere_from_fft(z_fft, z_fft) + chi_fft = - oper.invlaplacian2_fft(oper.fft2(mamp_zz)) + mamp_zchi = oper.monge_ampere_from_fft(z_fft, chi_fft) + Nw_fft = oper.fft2(mamp_zchi) + + if self.params.FORCING: + forcing_fft = self.forcing.get_forcing() + forcing_w_fft = forcing_fft... + Nw_fft += forcing_w_fft + + oper.dealiasing(Nw_fft) + + tendencies_fft = SetOfVariables( + like_this_sov=self.state.state_fft, + name_type_variables='tendencies_nonlin') + + tendencies_fft['ap_fft'] = -Nw_fft + tendencies_fft['am_fft'] = Nw_fft + + tendencies_fft /= -2j*self._tilde_Omega + + # ratio = self.test_tendencies_nonlin( + # tendencies_fft, w_fft, z_fft, chi_fft) + # print('ratio:', ratio) + + + + return tendencies_fft + + def compute_freq_diss(self): + """Compute the dissipation frequencies with dissipation only for w.""" + f_d_w, f_d_hypo_w = super(Simul, self).compute_freq_diss() + f_d = np.zeros_like(self.state.state_fft.data, dtype=np.float64) + f_d_hypo = np.zeros_like(self.state.state_fft.data, + dtype=np.float64) + f_d[0] = f_d_w + f_d_hypo[0] = f_d_hypo_w + return f_d, f_d_hypo + + # def test_tendencies_nonlin( + # self, tendencies_fft=None, + # w_fft=None, z_fft=None, chi_fft=None): + # r"""Test if the tendencies conserves the total energy. + + # We consider the conservative Föppl-von Kármán equations + # (without dissipation and forcing) written as + + # .. math:: + + # \p_t z = F_z + + # \p_t w = F_w + + # We have: + + # .. math:: + + # \p_t E_K(\mathbf{k}) = \mathcal{R} ( \hat F_w \hat w ^* ) + + # \p_t E_L(\mathbf{k}) = k^4 \mathcal{R} ( \hat F_z \hat z ^* ) + + # \p_t E_{NQ}(\mathbf{k}) = + # - \mathcal{R} ( \widehat{\{ F_z, z\}} \hat \chi ^* ) + + # Since the total energy is conserved, we should have + + # .. math:: + + # \sum_{\mathbf{k}} \p_t E_K(\mathbf{k}) + \p_t E_L(\mathbf{k}) + # + \p_t E_{NQ}(\mathbf{k}) = 0 + + # This function computes this quantities. + + # """ + + # if tendencies_fft is None: + # tendencies_fft = self.tendencies_nonlin() + # w_fft = self.state.state_fft['w_fft'] + # z_fft = self.state.state_fft['z_fft'] + # chi_fft = self.state.compute('chi_fft') + + # F_w_fft = tendencies_fft['w_fft'] + # F_z_fft = tendencies_fft['z_fft'] + + # K4 = self.oper.K4 + + # dt_E_K = np.real(F_w_fft * w_fft.conj()) + # dt_E_L = K4 * np.real(F_z_fft * z_fft.conj()) + + # tmp = self.oper.monge_ampere_from_fft(F_z_fft, z_fft) + # tmp_fft = self.oper.fft2(tmp) + + # dt_E_NQ = - np.real(tmp_fft * chi_fft.conj()) + + # T = dt_E_K + dt_E_L + dt_E_NQ + + # norm = self.oper.sum_wavenumbers(abs(T)) + + # if norm < 1e-15: + # print('Only zeros in total energy tendency.') + # # print('(K+L)\n', dt_E_K+dt_E_L) + # # print('NQ\n', dt_E_NQ) + # return 0 + # else: + # T = T/norm + # # print('ratio array\n', T) + # # print('(K+L)\n', (dt_E_K+dt_E_L)/norm) + # # print('NQ\n', dt_E_NQ/norm) + # return self.oper.sum_wavenumbers(T) + + +if __name__ == "__main__": + + np.set_printoptions(precision=2) + + import fluiddyn as fld + + params = fld.simul.create_params(info_solver) + + params.short_name_type_run = 'test' + + nh = 192/2 + Lh = 2*np.pi + params.oper.nx = nh + params.oper.ny = nh + params.oper.Lx = Lh + params.oper.Ly = Lh + # params.oper.type_fft = 'FFTWPY' + params.oper.coef_dealiasing = 2./3 + + delta_x = params.oper.Lx/params.oper.nx + params.nu_8 = 2.*10e-4*params.forcing.forcing_rate**(1./3)*delta_x**8 + + kmax = np.sqrt(2)*np.pi/delta_x + + params.time_stepping.USE_CFL = False + params.time_stepping.deltat0 = 2*np.pi/kmax**2 + params.time_stepping.USE_T_END = True + params.time_stepping.t_end = 50.0 + params.time_stepping.it_end = 1 + + # params.init_fields.type_flow_init = 'HARMONIC' + params.init_fields.type_flow_init = 'NOISE' + params.init_fields.max_velo_noise = 0.001 + # params.init_fields.path_file = ( + # '/home/users/bonamy2c/Sim_data/PLATE2D_test_L=' + # '2pix2pi_256x256_2015-03-04_22-36-37/state_phys_t=000.100.hd5') + + params.FORCING = True + params.forcing.forcing_rate = 100. + # params.forcing.nkmax_forcing = 5 + # params.forcing.nkmin_forcing = 4 + + params.output.periods_print.print_stdout = 0.5 + + params.output.periods_save.phys_fields = 0.0 + params.output.periods_save.spectra = 0.5 + # params.output.periods_save.spect_energy_budg = 0.5 + # params.output.periods_save.increments = 0.5 + + params.output.ONLINE_PLOT_OK = False + params.output.period_show_plot = 0.5 + params.output.periods_plot.phys_fields = 0.0 + + params.output.phys_fields.field_to_plot = 'z' + + params.output.spectra.HAS_TO_PLOT_SAVED = True + + sim = Simul(params) + + # sim.output.phys_fields.plot() + sim.time_stepping.start() + sim.output.phys_fields.plot() + + fld.show() diff --git a/fluidsim/solvers/plate2d/forcing.py b/fluidsim/solvers/plate2d/forcing.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL2ZvcmNpbmcucHk= --- /dev/null +++ b/fluidsim/solvers/plate2d/forcing.py @@ -0,0 +1,93 @@ +""" +Plate2d forcing (:mod:`fluidsim.solvers.plate2d.forcing`) +=============================================================== + +""" + +# import numpy as np + +# from fluiddyn.util import mpi + +from fluidsim.base.forcing import ForcingBasePseudoSpectral + +from fluidsim.base.forcing.specific import \ + Proportional as ProportionalBase + +from fluidsim.base.forcing.specific import \ + TimeCorrelatedRandomPseudoSpectral as TCRandomPS + + +class ForcingPlate2D(ForcingBasePseudoSpectral): + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + ForcingBasePseudoSpectral._complete_info_solver(info_solver) + classes = info_solver.classes.Forcing.classes + + package = 'fluidsim.solvers.plate2d.forcing' + + classes.set_child( + 'Random', + attribs={'module_name': package, + 'class_name': 'Random'}) + + classes.set_child( + 'Proportional', + attribs={'module_name': package, + 'class_name': 'Proportional'}) + + +class Random(TCRandomPS): + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + TCRandomPS._complete_params_with_default(params) + params.forcing.key_forced = 'w_fft' + + +class Proportional(ProportionalBase): + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + params.forcing.key_forced = 'w_fft' + + +class TimeCorrelatedRandomPseudoSpectralGauss(TCRandomPS): + def compute_forcingc_raw(self): + Fw_fft = super(TCRandomPS, self).compute_forcingc_raw() + + return Fw_fft + + # def compute_forcing_2nd_degree_eq(self): + # """compute a forcing normalize with a 2nd degree eq.""" + + # w_fft = self.sim.state.state_fft['w_fft'] + # vmax = self.sim.params.forcing.vmax + # n0 = w_fft.shape[0] + # n1 = w_fft.shape[1] + # zf = np.zeros((n0, n1), dtype=np.complex128) + # kfSI = 2*np.pi*2.5 + # sk = 1. + # kfor = self.sim.oper.Lx*kfSI/(2*np.pi) + # rand = np.random.normal(loc=0.0, scale=1.0, size=(2*n0/3, 2*n1/3)) + # for i0 in xrange(2*n0/3): + # for i1 in xrange(2*n1/3): + # zf[i0, i1] = ((1/(2*sk**2)) * + # (np.exp(-np.sqrt(i0**2+i1**2)-kfor-1)**2) * + # np.exp(2*np.pi*1j*rand[i0, i1])) + + # # zIJ(1:(n3+1),1:(n3+1)) = + # # exp(-(sqrt(I.^2+J.^2)-kfor-1).^2/(2*sk^2)) + # # .*exp(2*pi*1i*rand(size(I))); + # forcingW_fft = self.oper.nx_loc**2 * vmax / np.sum(zf) * zf + # self.forcingc_fft['w_fft'] = forcingW_fft + + # self.put_forcingc_in_forcing() + + diff --git a/fluidsim/solvers/plate2d/init_fields.py b/fluidsim/solvers/plate2d/init_fields.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL2luaXRfZmllbGRzLnB5 --- /dev/null +++ b/fluidsim/solvers/plate2d/init_fields.py @@ -0,0 +1,102 @@ +""" +Plate2d InitFields (:mod:`fluidsim.solvers.plate2d.init_fields`) +====================================================================== + + +""" + +import numpy as np + +from fluiddyn.util import mpi +from fluidsim.base.init_fields import InitFieldsBase + + +class InitFieldsPlate2D(InitFieldsBase): + """Init the fields for the solver PLATE2D.""" + + implemented_flows = ['NOISE', 'CONSTANT', 'LOAD_FILE', 'HARMONIC'] + + def __call__(self): + """Init the state (in physical and Fourier space) and time""" + sim = self.sim + + type_flow_init = self.get_and_check_type_flow_init() + + if type_flow_init == 'HARMONIC': + w_fft, z_fft = self.init_fields_harmonic() + tasks_complete_init = ['Fourier_to_phys'] + elif type_flow_init == 'NOISE': + w_fft, z_fft = self.init_fields_noise() + tasks_complete_init = ['Fourier_to_phys'] + elif type_flow_init == 'LOAD_FILE': + self.get_state_from_file(self.params.init_fields.path_file) + tasks_complete_init = [] + elif type_flow_init == 'CONSTANT': + # rot_fft = sim.oper.constant_arrayK(value=0.) + # if mpi.rank == 0: + # rot_fft[1, 0] = 1. + tasks_complete_init = ['Fourier_to_phys'] + else: + raise ValueError('bad value of params.type_flow_init') + + if 'Fourier_to_phys' in tasks_complete_init: + sim.oper.dealiasing(w_fft) + sim.oper.dealiasing(z_fft) + sim.state.state_fft['w_fft'] = w_fft + sim.state.state_fft['z_fft'] = z_fft + + sim.state.statephys_from_statefft() + + def init_fields_harmonic(self): + w_fft = np.zeros(self.sim.oper.shapeK_loc, dtype=np.complex128) + z_fft = np.zeros(self.sim.oper.shapeK_loc, dtype=np.complex128) + w_fft[20, 25] = 1. + z_fft[20, 25] = 1. + + w = self.oper.ifft2(w_fft) + z = self.oper.ifft2(z_fft) + + w_fft = self.oper.fft2(w) + z_fft = self.oper.fft2(z) + + return w_fft, z_fft + + def init_fields_noise(self): + try: + lambda0 = self.params.init_fields.lambda_noise + except AttributeError: + lambda0 = self.oper.Lx/4 + + def H_smooth(x, delta): + return (1. + np.tanh(2*np.pi*x/delta))/2 + + # to compute always the same field... (for 1 resolution...) + np.random.seed(42) # this does not work for MPI... + + w_fft = (np.random.random(self.oper.shapeK) + + 1j*np.random.random(self.oper.shapeK) - 0.5 - 0.5j) + z_fft = (np.random.random(self.oper.shapeK) + + 1j*np.random.random(self.oper.shapeK) - 0.5 - 0.5j) + + if mpi.rank == 0: + w_fft[0, 0] = 0. + z_fft[0, 0] = 0. + + self.oper.dealiasing(w_fft, z_fft) + + k0 = 2*np.pi/lambda0 + delta_k0 = 1.*k0 + w_fft = w_fft*H_smooth(k0-self.oper.KK, delta_k0) + z_fft = z_fft*H_smooth(k0-self.oper.KK, delta_k0) + + w = self.oper.ifft2(w_fft) + z = self.oper.ifft2(z_fft) + velo_max = np.sqrt(w**2+z**2).max() + if mpi.nb_proc > 1: + velo_max = self.oper.comm.allreduce(velo_max, op=mpi.MPI.MAX) + w = self.params.init_fields.max_velo_noise*w/velo_max + z = self.params.init_fields.max_velo_noise*z/velo_max + w_fft = self.oper.fft2(w) + z_fft = self.oper.fft2(z) + + return w_fft, z_fft diff --git a/fluidsim/solvers/plate2d/output/__init__.py b/fluidsim/solvers/plate2d/output/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL291dHB1dC9fX2luaXRfXy5weQ== --- /dev/null +++ b/fluidsim/solvers/plate2d/output/__init__.py @@ -0,0 +1,88 @@ +""" +Plate2d output (:mod:`fluidsim.solvers.plate2d.output`) +============================================================= + +.. currentmodule:: fluidsim.solvers.plate2d.output + +Provides: + +.. autosummary:: + :toctree: + + print_stdout + spatial_means + spectra + correlations_freq + +""" + +import numpy as np + + +from fluidsim.base.output import OutputBasePseudoSpectral + + +class Output(OutputBasePseudoSpectral): + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + info_solver.classes.Output.set_child('classes') + classes = info_solver.classes.Output.classes + + package = 'fluidsim.solvers.plate2d.output' + + classes.set_child( + 'PrintStdOut', + attribs={'module_name': package + '.print_stdout', + 'class_name': 'PrintStdOutPlate2D'}) + + classes.set_child( + 'PhysFields', + attribs={'module_name': 'fluidsim.base.output.phys_fields', + 'class_name': 'PhysFieldsBase'}) + + classes.set_child( + 'Spectra', + attribs={'module_name': package + '.spectra', + 'class_name': 'SpectraPlate2D'}) + + classes.set_child( + 'spatial_means', + attribs={'module_name': package + '.spatial_means', + 'class_name': 'SpatialMeansPlate2D'}) + + # classes.set_child( + # 'spatial_means', + # attribs={'module_name': package + '.correlations_freq', + # 'class_name': 'CorrelationsFreq'}) + + @staticmethod + def _complete_params_with_default(params, info_solver): + """This static method is used to complete the *params* container. + """ + OutputBasePseudoSpectral._complete_params_with_default( + params, info_solver) + + params.output.phys_fields.field_to_plot = 'z' + + def compute_energies_fft(self): + w_fft = self.sim.state.state_fft['w_fft'] + z_fft = self.sim.state.state_fft['z_fft'] + chi_fft = self.sim.state.compute('chi_fft') + Ee_fft = np.abs( + 0.25*self.sim.oper.laplacian2_fft(np.abs(chi_fft)**2+0j)) + El_fft = np.abs(0.5*self.sim.oper.laplacian2_fft(np.abs(z_fft)**2+0j)) + Ek_fft = 0.5*np.abs(w_fft)**2 + return Ek_fft, El_fft, Ee_fft + + def compute_energy_fft(self): + Ek_fft, El_fft, Ee_fft = self.compute_energies_fft() + return Ek_fft + El_fft + Ee_fft + + def compute_energy(self): + E_fft = self.compute_energy_fft() + return self.sum_wavenumbers(E_fft) diff --git a/fluidsim/solvers/plate2d/output/correl_Mordant.m b/fluidsim/solvers/plate2d/output/correl_Mordant.m new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL291dHB1dC9jb3JyZWxfTW9yZGFudC5t --- /dev/null +++ b/fluidsim/solvers/plate2d/output/correl_Mordant.m @@ -0,0 +1,38 @@ +% interactions 2<->2 F1+F3=F2+F4 + +% version NMordant (moins rapide) +for if1=ID2 %boucle F1 fix + if1 + F1(j)=if1; + + % boucle F2>F1 et F4>F2 car Cor4 est symetrique par echange F2 F4 + % Cor4(F4,F2,F1) + for if2=if1:1:newN + + afnew3=afnew(:,2*if2-if1:end); + afnew4=afnew(:,if2:end+if1-if2); + + AF1=repmat(afnew(:,if1),[1,newN-2*if2+if1+1]); + AF2=repmat(afnew(:,if2),[1,newN-2*if2+if1+1]); + + Cor4(if2:(newN-if2+if1),if2,j)=Cor4(if2:(newN-if2+if1),if2,j)+ (sum(conj(afnew3.*AF1).*(afnew4.*AF2),1)).'; + end + + % boucle F2<F1 + for if2=1:if1 + + afnew3=afnew(:,1:end-if1+if2); + afnew4=afnew(:,if1-if2+1:end); + + AF1=repmat(afnew(:,if1),[1,newN-if1+if2]); + AF2=repmat(afnew(:,if2),[1,newN-if1+if2]); + + Cor4(if1-if2+1:end,if2,j)=Cor4(if1-if2+1:end,if2,j)+ (sum(conj(afnew3.*AF1).*(afnew4.*AF2),1)).'; + end + + j=j+1; + +end + + + diff --git a/fluidsim/solvers/plate2d/output/correlations_freq.py b/fluidsim/solvers/plate2d/output/correlations_freq.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL291dHB1dC9jb3JyZWxhdGlvbnNfZnJlcS5weQ== --- /dev/null +++ b/fluidsim/solvers/plate2d/output/correlations_freq.py @@ -0,0 +1,203 @@ +""" +Correl freq (:mod:`fluidsim.solvers.plate2d.output.correlations_freq`) +============================================================================ + +.. currentmodule:: fluidsim.solvers.plate2d.output.correlations_freq + +Provides: + +.. autoclass:: CorrelationsFreq + :members: + :private-members: + +""" +import h5py + +import os +import numpy as np + +from fluiddyn.util import mpi + +from fluidsim.base.output.base import SpecificOutput + + +class CorrelationsFreq(SpecificOutput): + """Compute, save, load and plot correlations of frequencies. + + """ + + _tag = 'correl' + + @staticmethod + def _complete_params_with_default(params): + tag = 'correl' + + params.output.periods_save.set_attrib(tag, 0) + params.output.set_child(tag, + attribs={ + 'HAS_TO_PLOT_SAVED': False, + 'nb_times': 1000}) + + def __init__(self, output): + params = output.sim.params + + self.nb_times = params.output.correl.nb_times + self.nb_omegas = self.nb_times/4 + + super(CorrelationsFreq, self).__init__( + output, + period_save=params.output.periods_save.correl, + has_to_plot_saved=params.output.correl.HAS_TO_PLOT_SAVED) + + def init_path_files(self): + path_run = self.output.path_run + self.path_file = path_run + '/correlations_freq.h5' + + def init_files(self, dico_arrays_1time=None): + correlations = self.compute() + if mpi.rank == 0: + if not os.path.exists(self.path_file): + dico_arrays_1time = {'kxE': self.sim.oper.kxE, + 'kyE': self.sim.oper.kyE} + self.create_file_from_dico_arrays( + self.path_file, correlations, dico_arrays_1time) + self.nb_saved_times = 1 + else: + with h5py.File(self.path_file, 'r') as f: + dset_times = f['times'] + self.nb_saved_times = dset_times.shape[0]+1 + # save the spectra in the file spectra1D.h5 + self.add_dico_arrays_to_file(self.path_file1D, + correlations) + + self.t_last_save = self.sim.time_stepping.t + + def online_save(self): + """Save the values at one time. """ + tsim = self.sim.time_stepping.t + if (tsim-self.t_last_save >= self.period_save): + self.t_last_save = tsim + correlations = self.compute() + if mpi.rank == 0: + # save the spectra in the file correlation_freq.h5 + self.add_dico_arrays_to_file(self.path_file, + correlations) + self.nb_saved_times += 1 + # if self.has_to_plot: + # self._online_plot(dico_spectra1D, dico_spectra2D) + + # if (tsim-self.t_last_show >= self.period_show): + # self.t_last_show = tsim + # self.axe.get_figure().canvas.draw() + + def compute(self): + """compute the values at one time.""" + if mpi.rank == 0: + dico_results = {} + return dico_results + + def init_online_plot(self): + fig, axe = self.output.figure_axe(numfig=4000000) + self.axe = axe + axe.set_xlabel('?') + axe.set_ylabel('?') + axe.set_title('Correlation, solver '+self.output.name_solver + + ', nh = {0:5d}'.format(self.nx)) + axe.hold(True) + + def _online_plot(self): + pass + + # def load2D_mean(self, tmin=None, tmax=None): + # f = h5py.File(self.path_file2D, 'r') + # dset_times = f['times'] + # times = dset_times[...] + # nt = len(times) + + # kh = f['khE'][...] + + # if tmin is None: + # imin_plot = 0 + # else: + # imin_plot = np.argmin(abs(times-tmin)) + + # if tmax is None: + # imax_plot = nt-1 + # else: + # imax_plot = np.argmin(abs(times-tmax)) + + # tmin = times[imin_plot] + # tmax = times[imax_plot] + + # print('compute mean of 2D spectra\n' + # ('tmin = {0:8.6g} ; tmax = {1:8.6g}' + # 'imin = {2:8d} ; imax = {3:8d}').format( + # tmin, tmax, imin_plot, imax_plot)) + + # dico_results = {'kh': kh} + # for key in f.keys(): + # if key.startswith('spectr'): + # dset_key = f[key] + # spect = dset_key[imin_plot:imax_plot+1].mean(0) + # dico_results[key] = spect + # return dico_results + + def plot(self): + pass + + def _compute_correl4(self, w): + r"""Compute the correlations 4. + + .. math:: + C_4(\omega_1, \omega_2, \omega_3, \omega_4) = + \langle + \tilde w(\omega_1, \mathbf{x}) + + \tilde w(\omega_2, \mathbf{x}) + + \tilde w(\omega_3, \mathbf{x})^* + + \tilde w(\omega_4, \mathbf{x})^* + + \rangle_\mathbf{x}, + + where + + .. math:: + \omega_2 = \omega_3 + \omega_4 - \omega_1 + + and :math:`\omega_1 > 0`, :math:`\omega_3 > 0` and + :math:`\omega_4 > 0`. Thus, this function produces an array + :math:`C_4(\omega_1, \omega_3, \omega_4)`. + + """ + + nt, ny, nx = w.shape + + w_fftt = self.oper_fft1.fft(w).reshape([nt, nx*ny]) + w_fftt_conj = w_fftt.conj() + + nb_omegas = self.nb_omegas + + iomegas1 = self.iomegas1 + + corr4 = np.empty([len(iomegas1), nb_omegas, nb_omegas]) + + for i1, io1 in enumerate(iomegas1): + # this loop could be parallelized (OMP) + for io3 in range(nb_omegas): + # we use the symmetry omega_3 <--> omega_4 + for io4 in range(0, io3+1): + tmp = (w_fftt[io1, :] * + w_fftt_conj[io3, :] * + w_fftt_conj[io4, :]) + io2 = io3 + io4 - io1 + if io2 < 0: + io2 = abs(io2) + corr4[i1, io3, io4] = np.mean(tmp*w_fftt_conj[io2, :]) + else: + corr4[i1, io3, io4] = np.mean(tmp*w_fftt[io3, :]) + # symmetry omega_3 <--> omega_4: + corr4[i1, io4, io3] = corr4[i1, io3, io4] + + # if mpi.nb_proc > 1: + # # reduce for mean: + # mpi.comm. + + return corr4 diff --git a/fluidsim/solvers/plate2d/output/correlations_freq_cy.pyx b/fluidsim/solvers/plate2d/output/correlations_freq_cy.pyx new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL291dHB1dC9jb3JyZWxhdGlvbnNfZnJlcV9jeS5weXg= --- /dev/null +++ b/fluidsim/solvers/plate2d/output/correlations_freq_cy.pyx @@ -0,0 +1,83 @@ +""" +Correl freq (:mod:`fluidsim.solvers.plate2d.output.correlations_freq`) +============================================================================ + +.. currentmodule:: fluidsim.solvers.plate2d.output.correlations_freq + +Provides: + +.. autoclass:: CorrelationsFreq + :members: + :private-members: + +""" +import h5py + +import numpy as np + +# from fluiddyn.util import mpi + +from .correlations_freq import CorrelationsFreq as CorrelationsFreqPython + + +class CorrelationsFreq(CorrelationsFreqPython): + """Compute, save, load and plot correlations of frequencies. + + """ + + def _compute_correl4(self, w): + r"""Compute the correlations 4. + + .. math:: + C_4(\omega_1, \omega_2, \omega_3, \omega_4) = + \langle + \tilde w(\omega_1, \mathbf{x}) + + \tilde w(\omega_2, \mathbf{x}) + + \tilde w(\omega_3, \mathbf{x})^* + + \tilde w(\omega_4, \mathbf{x})^* + + \rangle_\mathbf{x}, + + where + + .. math:: + \omega_2 = \omega_3 + \omega_4 - \omega_1 + + and :math:`\omega_1 > 0`, :math:`\omega_3 > 0` and + :math:`\omega_4 > 0`. Thus, this function produces an array + :math:`C_4(\omega_1, \omega_3, \omega_4)`. + + """ + + nt, ny, nx = w.shape + + w_fftt = self.oper_fft1.fft(w).reshape([nt, nx*ny]) + w_fftt_conj = w_fftt.conj() + + nb_omegas = self.nb_omegas + + iomegas1 = self.iomegas1 + + corr4 = np.empty([len(iomegas1), nb_omegas, nb_omegas]) + + for i1, io1 in enumerate(iomegas1): + # this loop could be parallelized (OMP) + for io3 in range(nb_omegas): + # we use the symmetry omega_3 <--> omega_4 + for io4 in range(0, io3+1): + tmp = (w_fftt[io1, :] * + w_fftt_conj[io3, :] * + w_fftt_conj[io4, :]) + io2 = io3 + io4 - io1 + if io2 < 0: + io2 = abs(io2) + corr4[i1, io3, io4] = np.mean(tmp*w_fftt_conj[io2, :]) + else: + corr4[i1, io3, io4] = np.mean(tmp*w_fftt[io3, :]) + # symmetry omega_3 <--> omega_4: + corr4[i1, io4, io3] = corr4[i1, io3, io4] + + # if mpi.nb_proc > 1: + # # reduce for mean: + # mpi.comm. + + return corr4 diff --git a/fluidsim/solvers/plate2d/output/print_stdout.py b/fluidsim/solvers/plate2d/output/print_stdout.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL291dHB1dC9wcmludF9zdGRvdXQucHk= --- /dev/null +++ b/fluidsim/solvers/plate2d/output/print_stdout.py @@ -0,0 +1,142 @@ +"""Standard output (:mod:`fluidsim.solvers.plate2d.output.print_stdout`) +============================================================================== + +.. currentmodule:: fluidsim.solvers.plate2d.output.print_stdout + +Provides: + +.. autoclass:: PrintStdOutPlate2D + :members: + :private-members: + +""" + +from __future__ import print_function, division + +from time import time +import numpy as np + +from fluidsim.base.output.print_stdout import PrintStdOutBase + +from fluiddyn.util import mpi + + +class PrintStdOutPlate2D(PrintStdOutBase): + """Used to print in both the stdout and the stdout.txt file, and also + to print simple info on the current state of the simulation. + + """ + + def online_print(self): + tsim = self.sim.time_stepping.t + if (tsim-self.t_last_print_info <= self.period_print): + return + + tsim = self.sim.time_stepping.t + itsim = self.sim.time_stepping.it + deltatsim = self.sim.time_stepping.deltat + + energy = self.output.compute_energy() + if mpi.rank == 0: + t_real_word = time() + if self.t_real_word_last == 0.: + duration_left = 0 + else: + if self.params.time_stepping.USE_T_END: + duration_left = int(np.round( + (self.params.time_stepping.t_end - tsim) + * (t_real_word-self.t_real_word_last) + / (tsim - self.t_last_print_info) + )) + else: + duration_left = int(np.round( + (self.params.time_stepping.it_end - itsim) + * (t_real_word-self.t_real_word_last) + )) + to_print = ( + 'it = {0:6d} ; t = {1:9.3f} ; deltat = {2:10.5g}\n'+ + ' energy = {3:9.3e} ; Delta energy = {4:+9.3e}\n'+ + ' estimated remaining duration = {5:6d} s') + to_print = to_print.format( + itsim, tsim, deltatsim, + energy, energy-self.energy_temp, + duration_left) + self.print_stdout(to_print) + self.t_real_word_last = t_real_word + self.energy_temp = energy + self.t_last_print_info = tsim + + def load(self): + dico_results = {'name_solver': self.output.name_solver} + file_means = open(self.output.path_run+'/stdout.txt') + lines = file_means.readlines() + + lines_t = [] + lines_E = [] + for il, line in enumerate(lines): + if line[0:4] == 'it =': + lines_t.append(line) + if line[0:22] == ' energy =': + lines_E.append(line) + + nt = len(lines_t) + if nt > 1: + nt -= 1 + + it = np.zeros(nt, dtype=np.int) + t = np.zeros(nt) + deltat = np.zeros(nt) + + E = np.zeros(nt) + deltaE = np.zeros(nt) + + for il in xrange(nt): + line = lines_t[il] + words = line.split() + it[il] = int(words[2]) + t[il] = float(words[6]) + deltat[il] = float(words[10]) + + line = lines_E[il] + words = line.split() + E[il] = float(words[2]) + deltaE[il] = float(words[7]) + + dico_results['it'] = it + dico_results['t'] = t + dico_results['deltat'] = deltat + dico_results['E'] = E + dico_results['deltaE'] = deltaE + + return dico_results + + def plot(self): + dico_results = self.load() + + t = dico_results['t'] + deltat = dico_results['deltat'] + E = dico_results['E'] + deltaE = dico_results['deltaE'] + + x_left_axe = 0.12 + z_bottom_axe = 0.55 + width_axe = 0.85 + height_axe = 0.4 + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('t') + ax1.set_ylabel('deltat(t)') + + ax1.set_title('info stdout, solver '+self.output.name_solver + + ', nh = {0:5d}'.format(self.nx)) + ax1.hold(True) + ax1.plot(t, deltat, 'k', linewidth=2) + + size_axe[1] = 0.08 + ax2 = fig.add_axes(size_axe) + ax2.set_xlabel('t') + ax2.set_ylabel('E(t), deltaE(t)') + ax2.hold(True) + ax2.plot(t, E, 'k', linewidth=2) + ax2.plot(t, deltaE, 'b', linewidth=2) diff --git a/fluidsim/solvers/plate2d/output/spatial_means.py b/fluidsim/solvers/plate2d/output/spatial_means.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL291dHB1dC9zcGF0aWFsX21lYW5zLnB5 --- /dev/null +++ b/fluidsim/solvers/plate2d/output/spatial_means.py @@ -0,0 +1,270 @@ +""" +Spatial means (:mod:`fluidsim.solvers.plate2d.output.spatial_means`) +========================================================================== + +.. currentmodule:: fluidsim.solvers.plate2d.output.spatial_means + +Provides: + +.. autoclass:: SpatialMeansPlate2D + :members: + :private-members: + +""" +from __future__ import division, print_function + +import os +import numpy as np + + +from fluiddyn.util import mpi + +from fluidsim.base.output.spatial_means import SpatialMeansBase + + +class SpatialMeansPlate2D(SpatialMeansBase): + r"""Compute, save, load and plot spatial means. + + .. |p| mathmacro:: \partial + + If only :math:`W` is forced and dissipated, the energy budget is + quite simple and can be written as: + + .. math:: + + \p_t E_W = - C_{W\rightarrow Z} - C_{W\rightarrow \chi} + P_W - D_W, + + \p_t E_Z = + C_{W\rightarrow Z}, + + \p_t E_\chi = + C_{W\rightarrow \chi}, + + where + + .. math:: + + C_{W\rightarrow Z} = \sum_{\mathbf{k}} k^4\mathcal{R}(\hat W \hat Z^*), + + C_{W\rightarrow \chi} = -\sum_{\mathbf{k}} + \mathcal{R}( \widehat{\{ W, Z\}} \hat \chi ^* ), + + P_W = \sum_{\mathbf{k}} \mathcal{R}( \hat F_W \hat W^* ) + + and + + .. math:: + + D_W = 2 \nu_\alpha \sum_{\mathbf{k}} k^{2\alpha} E_K(k). + +""" + + def save_one_time(self): + tsim = self.sim.time_stepping.t + self.t_last_save = tsim + + energy_fft = self.output.compute_energy_fft() + energy = self.sum_wavenumbers(energy_fft) + + f_d, f_d_hypo = self.sim.time_stepping.compute_freq_diss() + epsK = self.sum_wavenumbers(f_d*2*energy_fft) + epsK_hypo = self.sum_wavenumbers(f_d_hypo*2*energy_fft) + + if self.sim.params.FORCING: + deltat = self.sim.time_stepping.deltat + Frot_fft = self.sim.forcing.get_forcing()['rot_fft'] + Fx_fft, Fy_fft = self.vecfft_from_rotfft(Frot_fft) + + rot_fft = self.sim.state.state_fft['rot_fft'] + ux_fft, uy_fft = self.vecfft_from_rotfft(rot_fft) + + PZ1_fft = np.real( + rot_fft.conj()*Frot_fft + + rot_fft*Frot_fft.conj())/2 + PZ2_fft = (abs(Frot_fft)**2)*deltat/2 + + PZ1 = self.sum_wavenumbers(PZ1_fft) + PZ2 = self.sum_wavenumbers(PZ2_fft) + + PK1_fft = np.real( + ux_fft.conj()*Fx_fft + + ux_fft*Fx_fft.conj() + + uy_fft.conj()*Fy_fft + + uy_fft*Fy_fft.conj())/2 + PK2_fft = (abs(Fx_fft)**2+abs(Fy_fft)**2)*deltat/2 + + PK1 = self.sum_wavenumbers(PK1_fft) + PK2 = self.sum_wavenumbers(PK2_fft) + + if mpi.rank == 0: + epsK_tot = epsK+epsK_hypo + + self.file.write( + '####\ntime = {0:7.3f}\n'.format(tsim)) + to_print = ( + 'E = {0:11.6e} \n' + 'epsK = {1:11.6e} ; epsK_hypo = {2:11.6e} ; ' + 'epsK_tot = {3:11.6e} \n').format( + energy, epsK, epsK_hypo, epsK+epsK_hypo) + self.file.write(to_print) + + if self.sim.params.FORCING: + PK_tot = PK1+PK2 + to_print = ( +'PK1 = {0:11.6e} ; PK2 = {1:11.6e} ; PK_tot = {2:11.6e} \n' +'PZ1 = {3:11.6e} ; PZ2 = {4:11.6e} ; PZ_tot = {5:11.6e} \n' +).format(PK1, PK2, PK1+PK2, PZ1, PZ2, PZ1+PZ2) + self.file.write(to_print) + + self.file.flush() + os.fsync(self.file.fileno()) + + if self.sim.params.output.spatial_means.has_to_plot and mpi.rank == 0: + + self.axe_a.plot(tsim, energy, 'k.') + + self.axe_b.plot(tsim, epsK_tot, 'k.') + if self.sim.params.FORCING: + self.axe_b.plot(tsim, PK_tot, 'm.') + + if (tsim-self.t_last_show >= self.period_show): + self.t_last_show = tsim + fig = self.axe_a.get_figure() + fig.canvas.draw() + + def load(self): + dico_results = {'name_solver': self.output.name_solver} + + file_means = open(self.path_file) + lines = file_means.readlines() + + lines_t = [] + lines_E = [] + lines_PK = [] + lines_PZ = [] + lines_epsK = [] + + for il, line in enumerate(lines): + if line.startswith('time ='): + lines_t.append(line) + if line.startswith('E ='): + lines_E.append(line) + if line.startswith('PK1 ='): + lines_PK.append(line) + if line.startswith('PZ1 ='): + lines_PZ.append(line) + if line.startswith('epsK ='): + lines_epsK.append(line) + + nt = len(lines_t) + if nt > 1: + nt -= 1 + + t = np.empty(nt) + E = np.empty(nt) + Z = np.empty(nt) + PK1 = np.empty(nt) + PK2 = np.empty(nt) + PK_tot = np.empty(nt) + PZ1 = np.empty(nt) + PZ2 = np.empty(nt) + PZ_tot = np.empty(nt) + epsK = np.empty(nt) + epsK_hypo = np.empty(nt) + epsK_tot = np.empty(nt) + + for il in xrange(nt): + line = lines_t[il] + words = line.split() + t[il] = float(words[2]) + + line = lines_E[il] + words = line.split() + E[il] = float(words[2]) + Z[il] = float(words[6]) + + if self.sim.params.FORCING: + line = lines_PK[il] + words = line.split() + PK1[il] = float(words[2]) + PK2[il] = float(words[6]) + PK_tot[il] = float(words[10]) + + line = lines_PZ[il] + words = line.split() + PZ1[il] = float(words[2]) + PZ2[il] = float(words[6]) + PZ_tot[il] = float(words[10]) + + line = lines_epsK[il] + words = line.split() + epsK[il] = float(words[2]) + epsK_hypo[il] = float(words[6]) + epsK_tot[il] = float(words[10]) + + dico_results['t'] = t + dico_results['E'] = E + dico_results['Z'] = Z + + dico_results['PK1'] = PK1 + dico_results['PK2'] = PK2 + dico_results['PK_tot'] = PK_tot + + dico_results['PZ1'] = PZ1 + dico_results['PZ2'] = PZ2 + dico_results['PZ_tot'] = PZ_tot + + dico_results['epsK'] = epsK + dico_results['epsK_hypo'] = epsK_hypo + dico_results['epsK_tot'] = epsK_tot + + return dico_results + + def plot(self): + dico_results = self.load() + + t = dico_results['t'] + E = dico_results['E'] + Z = dico_results['Z'] + + PK_tot = dico_results['PK_tot'] + + epsK = dico_results['epsK'] + epsK_hypo = dico_results['epsK_hypo'] + epsK_tot = dico_results['epsK_tot'] + + width_axe = 0.85 + height_axe = 0.4 + x_left_axe = 0.12 + z_bottom_axe = 0.55 + + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('t') + ax1.set_ylabel('E(t)') + ax1.hold(True) + ax1.plot(t, E, 'k', linewidth=2) + + z_bottom_axe = 0.08 + size_axe[1] = z_bottom_axe + ax2 = fig.add_axes(size_axe) + ax2.set_xlabel('t') + ax2.set_ylabel('Z(t)') + ax2.hold(True) + ax2.plot(t, Z, 'k', linewidth=2) + + z_bottom_axe = 0.55 + size_axe[1] = z_bottom_axe + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('t') + ax1.set_ylabel('P_E(t), epsK(t)') + ax1.hold(True) + ax1.plot(t, PK_tot, 'c', linewidth=2) + ax1.plot(t, epsK, 'r', linewidth=2) + ax1.plot(t, epsK_hypo, 'g', linewidth=2) + ax1.plot(t, epsK_tot, 'k', linewidth=2) + + + + + + diff --git a/fluidsim/solvers/plate2d/output/spectra.py b/fluidsim/solvers/plate2d/output/spectra.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL291dHB1dC9zcGVjdHJhLnB5 --- /dev/null +++ b/fluidsim/solvers/plate2d/output/spectra.py @@ -0,0 +1,203 @@ +"""Spectra (:mod:`fluidsim.solvers.plate2d.output.spectra`) +================================================================= + +.. currentmodule:: fluidsim.solvers.plate2d.output.spectra + +Provides: + +.. autoclass:: SpectraPlate2D + :members: + :private-members: + +""" + +import h5py + +import numpy as np + +from fluidsim.base.output.spectra import Spectra + + +class SpectraPlate2D(Spectra): + """Compute, save, load and plot spectra.""" + + def compute(self): + """compute the values at one time.""" + EK_fft, EL_fft, EE_fft = self.output.compute_energies_fft() + # compute the spectra 1D + spectrum1Dkx_EK, spectrum1Dky_EK = self.spectra1D_from_fft(EK_fft) + spectrum1Dkx_EL, spectrum1Dky_EL = self.spectra1D_from_fft(EL_fft) + spectrum1Dkx_EE, spectrum1Dky_EE = self.spectra1D_from_fft(EE_fft) + + dico_spectra1D = {'spectrum1Dkx_EK': spectrum1Dkx_EK, + 'spectrum1Dky_EK': spectrum1Dky_EK, + 'spectrum1Dkx_EL': spectrum1Dkx_EL, + 'spectrum1Dky_EL': spectrum1Dky_EL, + 'spectrum1Dkx_EE': spectrum1Dkx_EE, + 'spectrum1Dky_EE': spectrum1Dky_EE} + + # compute the spectra 2D + spectrum2D_EK = self.spectrum2D_from_fft(EK_fft) + spectrum2D_EL = self.spectrum2D_from_fft(EL_fft) + spectrum2D_EE = self.spectrum2D_from_fft(EE_fft) + dico_spectra2D = {'spectrum2D_EK': spectrum2D_EK, + 'spectrum2D_EL': spectrum2D_EL, + 'spectrum2D_EE': spectrum2D_EE} + return dico_spectra1D, dico_spectra2D + + def _online_plot(self, dico_spectra1D, dico_spectra2D): + if (self.nx == self.params.oper.ny and + self.params.oper.Lx == self.params.oper.Ly): + spectrum2D_EK = dico_spectra2D['spectrum2D_EK'] + spectrum2D_EL = dico_spectra2D['spectrum2D_EL'] + spectrum2D_EE = dico_spectra2D['spectrum2D_EE'] + spectrum2D_Etot = (spectrum2D_EK + spectrum2D_EL + spectrum2D_EE) + khE = self.oper.khE + coef_norm = khE**(3.) + self.axe.loglog(khE, spectrum2D_Etot*coef_norm, 'k', linewidth=2) + self.axe.loglog(khE, spectrum2D_EK*coef_norm, 'b--') + self.axe.loglog(khE, spectrum2D_EL*coef_norm, 'r--') + self.axe.loglog(khE, spectrum2D_EE*coef_norm, 'y--') + lin_inf, lin_sup = self.axe.get_ylim() + if lin_inf < 10e-6: + lin_inf = 10e-6 + self.axe.set_ylim([lin_inf, lin_sup]) + else: + print('you need to implement the ploting ' + 'of the spectra for this case') + + def plot1D(self, tmin=0, tmax=1000, delta_t=2, + coef_compensate=3): + + f = h5py.File(self.path_file1D, 'r') + dset_times = f['times'] + + dset_kxE = f['kxE'] + # dset_kyE = f['kyE'] + kh = dset_kxE[...] + + dset_spectrum1Dkx_EK = f['spectrum1Dkx_EK'] + dset_spectrum1Dky_EK = f['spectrum1Dky_EK'] + # dset_spectrum1Dkx_EL = f['spectrum1Dkx_EL'] + # dset_spectrum1Dky_EL = f['spectrum1Dky_EL'] + # dset_spectrum1Dkx_EE = f['spectrum1Dkx_EE'] + # dset_spectrum1Dky_EE = f['spectrum1Dky_EE'] + + times = dset_times[...] + + delta_t_save = np.mean(times[1:]-times[0:-1]) + delta_i_plot = int(np.round(delta_t/delta_t_save)) + delta_t = delta_t_save*delta_i_plot + if delta_i_plot == 0: + delta_i_plot = 1 + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + + print( + 'plot1D(tmin={0}, tmax={1}, delta_t={2:.2f},'.format( + tmin, tmax, delta_t) + + ' coef_compensate={0:.3f})'.format(coef_compensate)) + + print(( + 'plot 1D spectra\n' + 'tmin = {0:8.6g} ; tmax = {1:8.6g} ; delta_t = {2:8.6g}\n' + 'imin = {3:8d} ; imax = {4:8d} ; delta_i = {5:8d}').format( + tmin_plot, tmax_plot, delta_t, + imin_plot, imax_plot, delta_i_plot)) + + fig, ax1 = self.output.figure_axe() + ax1.set_xlabel('$k_h$') + ax1.set_ylabel('spectra') + ax1.set_title('1D spectra, solver '+self.output.name_solver + + ', nh = {0:5d}'.format(self.nx)) + ax1.hold(True) + ax1.set_xscale('log') + ax1.set_yscale('log') + + coef_norm = kh**(coef_compensate) + if delta_t != 0.: + for it in xrange(imin_plot, imax_plot+1, delta_i_plot): + EK = (dset_spectrum1Dkx_EK[it]+dset_spectrum1Dky_EK[it]) + EK[EK < 10e-16] = 0. + ax1.plot(kh, EK*coef_norm, 'b', linewidth=1) + + EK = (dset_spectrum1Dkx_EK[imin_plot:imax_plot+1] + + dset_spectrum1Dky_EK[imin_plot:imax_plot+1]).mean(0) + + ax1.plot(kh, kh**(-3)*coef_norm, 'k', linewidth=1) + ax1.plot(kh, 0.01*kh**(-5/3)*coef_norm, 'k--', linewidth=1) + + def plot2D(self, tmin=0, tmax=1000, delta_t=2, + coef_compensate=3): + f = h5py.File(self.path_file2D, 'r') + dset_times = f['times'] + # nb_spectra = dset_times.shape[0] + times = dset_times[...] + # nt = len(times) + + kh = f['khE'][...] + + dset_spectrum_EK = f['spectrum2D_EK'] + dset_spectrum_EL = f['spectrum2D_EL'] + dset_spectrum_EE = f['spectrum2D_EE'] + + delta_t_save = np.mean(times[1:]-times[0:-1]) + delta_i_plot = int(np.round(delta_t/delta_t_save)) + if delta_i_plot == 0 and delta_t != 0.: + delta_i_plot = 1 + delta_t = delta_i_plot*delta_t_save + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + + print( + 'plot2D(tmin={0}, tmax={1}, delta_t={2:.2f},'.format( + tmin, tmax, delta_t) + + ' coef_compensate={0:.3f})'.format(coef_compensate)) + + print(( + 'plot 2D spectra\n' + 'tmin = {0:8.6g} ; tmax = {1:8.6g} ; delta_t = {2:8.6g}\n' + 'imin = {3:8d} ; imax = {4:8d} ; delta_i = {5:8d}').format( + tmin_plot, tmax_plot, delta_t, + imin_plot, imax_plot, delta_i_plot)) + + fig, ax1 = self.output.figure_axe() + ax1.set_xlabel('$k_h$') + ax1.set_ylabel('2D spectra') + ax1.set_title('2D spectra, solver ' + self.output.name_solver + + ', nh = {0:5d}'.format(self.nx)) + ax1.hold(True) + ax1.set_xscale('log') + ax1.set_yscale('log') + + coef_norm = kh**coef_compensate + + if delta_t != 0.: + for it in xrange(imin_plot, imax_plot+1, delta_i_plot): + EK = dset_spectrum_EK[it] + EK[EK < 10e-16] = 0. + EL = dset_spectrum_EL[it] + EL[EL < 10e-16] = 0. + EE = dset_spectrum_EE[it] + EE[EE < 10e-16] = 0. + Etot = EK + EL + EE + + ax1.plot(kh, Etot*coef_norm, 'k', linewidth=2) + ax1.plot(kh, EK*coef_norm, 'b--', linewidth=1) + ax1.plot(kh, EL*coef_norm, 'r--', linewidth=1) + ax1.plot(kh, EE*coef_norm, 'y', linewidth=1) + + EK = dset_spectrum_EK[imin_plot:imax_plot+1].mean(0) + EK[EK < 10e-16] = 0. + ax1.plot(kh, EK*coef_norm, 'b-', linewidth=2) + + ax1.plot(kh, kh**(-3)*coef_norm, 'k--', linewidth=1) + ax1.plot(kh, 0.01*kh**(-5./3)*coef_norm, 'k-.', linewidth=1) diff --git a/fluidsim/solvers/plate2d/solver.py b/fluidsim/solvers/plate2d/solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL3NvbHZlci5weQ== --- /dev/null +++ b/fluidsim/solvers/plate2d/solver.py @@ -0,0 +1,333 @@ +# -*- coding: utf-8 -*- + +"""Plate2d solver (:mod:`fluidsim.solvers.plate2d.solver`) +================================================================ + +.. currentmodule:: fluidsim.solvers.plate2d.solver + +Provides: + +.. autoclass:: Simul + :members: + :private-members: + +.. todo:: + + - Compile without fftw-mpi, + - bench performances, + - output: + * spectra, + * spatial means (energy, dissipation, forcing), + * correlations frequencies, + - forcing, + - solver solving exactly the linear terms. + +""" + +from __future__ import print_function + +import numpy as np + +from fluidsim.operators.setofvariables import SetOfVariables +from fluidsim.base.solvers.pseudo_spect import ( + SimulBasePseudoSpectral, InfoSolverPseudoSpectral) + + +info_solver = InfoSolverPseudoSpectral() + +package = 'fluidsim.solvers.plate2d' +info_solver.module_name = package + '.solver' +info_solver.class_name = 'Simul' +info_solver.short_name = 'Plate2D' + +classes = info_solver.classes + +classes.State.module_name = package + '.state' +classes.State.class_name = 'StatePlate2D' + +classes.InitFields.module_name = package + '.init_fields' +classes.InitFields.class_name = 'InitFieldsPlate2D' + +classes.Output.module_name = package + '.output' +classes.Output.class_name = 'Output' + +classes.Forcing.module_name = package + '.forcing' +classes.Forcing.class_name = 'ForcingPlate2D' + + +info_solver.complete_with_classes() + + +class Simul(SimulBasePseudoSpectral): + r"""Pseudo-spectral solver solving the Föppl-von Kármán equations. + + Notes + ----- + + .. |p| mathmacro:: \partial + + This class is dedicated to solve with a pseudo-spectral method the + Föppl-von Kármán equations which describe the dynamics of a rigid + plate. Using the non-dimensional variables displacement :math:`Z` + and out of plane velocity :math:`W`: + + .. math:: + \p_t Z = W, + + .. math:: + \p_t W = - \Delta^2 Z + N_W(Z) + F f_W - \nu_\alpha (-\Delta)^\alpha W. + + where :math:`\Delta = \p_{xx} + \p_{yy}` is the Laplacian. The + first term of the two equations corresponds to the linear part. + :math:`F f_W` and :math:`\nu_\alpha \Delta^\alpha W` are the + forcing and the dissipation terms, respectively. The nonlinear + term is equal to :math:`N_W(Z) = \{ Z, \chi \}`, where :math:`\{ + \cdot, \cdot \}` is the Monge-Ampère operator + + .. math:: + \{ a, b \} = \p_{xx} a \p_{yy} b + \p_{yy} a \p_{xx} b + - 2 \p_{xy} a \p_{xy} b, + + and + + .. math:: \Delta^2 \chi = -\{ Z, Z \}. + + Taking the Fourier transform, we get: + + .. math:: + \p_t \hat Z = \hat W, + + .. math:: + \p_t \hat W = - k^4 \hat Z + \widehat{N_W(Z)} + F \hat f_W + - \nu_\alpha k^{2\alpha} \hat W, + + where :math:`k^2 = |\mathbf{k}|^2`. For this simple solver, we + will use the variables :math:`Z` and :math:`W` and only the + dissipative term will be solve exactly. Thus, all the other terms + are included in the :func:`tendencies_nonlin` function. + + **Energetics**: The total energy can be decomposed in the kinetic energy + + .. math:: + E_K = \frac{1}{2} \langle W^2 \rangle + = \frac{1}{2} \sum_\mathbf{k} |\hat W|^2, + + the flexion energy + + .. math:: + E_L = \frac{1}{2} \langle (\Delta Z)^2 \rangle + = \frac{1}{2} \sum_\mathbf{k} k^4|\hat Z|^2, + + and the non-quadratic extension energy + + .. math:: + E_E = \frac{1}{4} \langle (\Delta \chi)^2 \rangle + = \frac{1}{4} \sum_\mathbf{k} k^4 |\hat \chi|^2. + + The energy injected into the system by the forcing is + + .. math:: + P = F \langle f W \rangle, + + and the dissipation is + + .. math:: + D = \nu_\alpha \langle W (-\Delta)^\alpha W \rangle. + + """ + + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + SimulBasePseudoSpectral._complete_params_with_default(params) + attribs = {'beta': 0.} + params.set_attribs(attribs) + + def __init__(self, params): + # the common initialization with the PLATE2D info_solver: + super(Simul, self).__init__(params, info_solver) + + def tendencies_nonlin(self, state_fft=None): + """Compute the "nonlinear" tendencies.""" + oper = self.oper + + if state_fft is None: + w_fft = self.state.state_fft['w_fft'] + z_fft = self.state.state_fft['z_fft'] + else: + w_fft = state_fft['w_fft'] + z_fft = state_fft['z_fft'] + + mamp_zz = oper.monge_ampere_from_fft(z_fft, z_fft) + chi_fft = - oper.invlaplacian2_fft(oper.fft2(mamp_zz)) + mamp_zchi = oper.monge_ampere_from_fft(z_fft, chi_fft) + Nw_fft = oper.fft2(mamp_zchi) + lap2z_fft = oper.laplacian2_fft(z_fft) + F_fft = - lap2z_fft + Nw_fft + + oper.dealiasing(F_fft) + oper.dealiasing(w_fft) + + tendencies_fft = SetOfVariables( + like_this_sov=self.state.state_fft, + name_type_variables='tendencies_nonlin') + + tendencies_fft['w_fft'] = F_fft + tendencies_fft['z_fft'] = w_fft + + # ratio = self.test_tendencies_nonlin( + # tendencies_fft, w_fft, z_fft, chi_fft) + # print('ratio:', ratio) + + if self.params.FORCING: + tendencies_fft += self.forcing.get_forcing() + + return tendencies_fft + + def compute_freq_diss(self): + """Compute the dissipation frequencies with dissipation only for w.""" + f_d_w, f_d_hypo_w = super(Simul, self).compute_freq_diss() + f_d = np.zeros_like(self.state.state_fft.data, dtype=np.float64) + f_d_hypo = np.zeros_like(self.state.state_fft.data, + dtype=np.float64) + f_d[0] = f_d_w + f_d_hypo[0] = f_d_hypo_w + return f_d, f_d_hypo + + def test_tendencies_nonlin( + self, tendencies_fft=None, + w_fft=None, z_fft=None, chi_fft=None): + r"""Test if the tendencies conserves the total energy. + + We consider the conservative Föppl-von Kármán equations + (without dissipation and forcing) written as + + .. math:: + + \p_t Z = F_Z + + \p_t W = F_W + + We have: + + .. math:: + + \p_t E_K(\mathbf{k}) = \mathcal{R} ( \hat F_W \hat W ^* ) + + \p_t E_L(\mathbf{k}) = k^4 \mathcal{R} ( \hat F_Z \hat Z ^* ) + + \p_t E_{NQ}(\mathbf{k}) = + - \mathcal{R} ( \widehat{\{ F_Z, Z\}} \hat \chi ^* ) + + Since the total energy is conserved, we should have + + .. math:: + + \sum_{\mathbf{k}} \p_t E_K(\mathbf{k}) + \p_t E_L(\mathbf{k}) + + \p_t E_{NQ}(\mathbf{k}) = 0 + + This function computes this quantities. + + """ + + if tendencies_fft is None: + tendencies_fft = self.tendencies_nonlin() + w_fft = self.state.state_fft['w_fft'] + z_fft = self.state.state_fft['z_fft'] + chi_fft = self.state.compute('chi_fft') + + F_w_fft = tendencies_fft['w_fft'] + F_z_fft = tendencies_fft['z_fft'] + + K4 = self.oper.K4 + + dt_E_K = np.real(F_w_fft * w_fft.conj()) + dt_E_L = K4 * np.real(F_z_fft * z_fft.conj()) + + tmp = self.oper.monge_ampere_from_fft(F_z_fft, z_fft) + tmp_fft = self.oper.fft2(tmp) + + dt_E_NQ = - np.real(tmp_fft * chi_fft.conj()) + + T = dt_E_K + dt_E_L + dt_E_NQ + + norm = self.oper.sum_wavenumbers(abs(T)) + + if norm < 1e-15: + print('Only zeros in total energy tendency.') + # print('(K+L)\n', dt_E_K+dt_E_L) + # print('NQ\n', dt_E_NQ) + return 0 + else: + T = T/norm + # print('ratio array\n', T) + # print('(K+L)\n', (dt_E_K+dt_E_L)/norm) + # print('NQ\n', dt_E_NQ/norm) + return self.oper.sum_wavenumbers(T) + + +if __name__ == "__main__": + + np.set_printoptions(precision=2) + + import fluiddyn as fld + + params = fld.simul.create_params(info_solver) + + params.short_name_type_run = 'test' + + nh = 192/2 + Lh = 2*np.pi + params.oper.nx = nh + params.oper.ny = nh + params.oper.Lx = Lh + params.oper.Ly = Lh + # params.oper.type_fft = 'FFTWPY' + params.oper.coef_dealiasing = 2./3 + + delta_x = params.oper.Lx/params.oper.nx + params.nu_8 = 2.*10e-4*params.forcing.forcing_rate**(1./3)*delta_x**8 + + kmax = np.sqrt(2)*np.pi/delta_x + + params.time_stepping.USE_CFL = False + params.time_stepping.deltat0 = 2*np.pi/kmax**2 + params.time_stepping.USE_T_END = True + params.time_stepping.t_end = 50.0 + params.time_stepping.it_end = 1 + + # params.init_fields.type_flow_init = 'HARMONIC' + params.init_fields.type_flow_init = 'NOISE' + params.init_fields.max_velo_noise = 0.001 + # params.init_fields.path_file = ( + # '/home/users/bonamy2c/Sim_data/PLATE2D_test_L=' + # '2pix2pi_256x256_2015-03-04_22-36-37/state_phys_t=000.100.hd5') + + params.FORCING = True + params.forcing.forcing_rate = 100. + # params.forcing.nkmax_forcing = 5 + # params.forcing.nkmin_forcing = 4 + + params.output.periods_print.print_stdout = 0.5 + + params.output.periods_save.phys_fields = 0.0 + params.output.periods_save.spectra = 0.5 + # params.output.periods_save.spect_energy_budg = 0.5 + # params.output.periods_save.increments = 0.5 + + params.output.ONLINE_PLOT_OK = False + params.output.period_show_plot = 0.5 + params.output.periods_plot.phys_fields = 0.0 + + params.output.phys_fields.field_to_plot = 'z' + + params.output.spectra.HAS_TO_PLOT_SAVED = True + + sim = Simul(params) + + # sim.output.phys_fields.plot() + sim.time_stepping.start() + sim.output.phys_fields.plot() + + fld.show() diff --git a/fluidsim/solvers/plate2d/state.py b/fluidsim/solvers/plate2d/state.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL3N0YXRlLnB5 --- /dev/null +++ b/fluidsim/solvers/plate2d/state.py @@ -0,0 +1,75 @@ +"""Plate2d state (:mod:`fluidsim.solvers.plate2d.state`) +============================================================== +""" + +from fluidsim.base.state import StatePseudoSpectral + +from fluiddyn.util import mpi + + +class StatePlate2D(StatePseudoSpectral): + """Contains the variables corresponding to the state and handles the + access to other fields for the solver PLATE2D. + + """ + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + info_solver.classes.State.set_attribs({ + 'keys_state_fft': ['w_fft', 'z_fft'], + 'keys_state_phys': ['w', 'z'], + 'keys_computable': [], + 'keys_phys_needed': ['w', 'z'], + 'keys_linear_eigenmodes': []}) + + def compute(self, key, SAVE_IN_DICT=True, RAISE_ERROR=True): + oper = self.oper + it = self.sim.time_stepping.it + if (key in self.vars_computed and + it == self.it_computed[key]): + return self.vars_computed[key] + + if key == 'w_fft': + result = oper.fft2(self.state_phys['w']) + elif key == 'z_fft': + result = oper.fft2(self.state_phys['z']) + elif key == 'chi_fft': + mamp_zz = oper.monge_ampere_from_fft( + self.state_fft['z_fft'], self.state_fft['z_fft']) + result = - oper.invlaplacian2_fft(oper.fft2(mamp_zz)) + elif key == 'chi': + chi_fft = self.compute('chi_fft') + result = oper.ifft2(chi_fft) + elif key == 'Nw_fft': + mamp_zchi = oper.monge_ampere_from_fft( + self.state_fft['z_fft'], self.compute('chi_fft')) + result = oper.fft2(mamp_zchi) + elif key == 'lapz_fft': + z_fft = self.compute('z_fft') + result = oper.laplacian2_fft(z_fft) + else: + to_print = 'Do not know how to compute "'+key+'".' + if RAISE_ERROR: + raise ValueError(to_print) + else: + if mpi.rank == 0: + print(to_print + + '\nreturn an array of zeros.') + + result = self.oper.constant_arrayX(value=0.) + + if SAVE_IN_DICT: + self.vars_computed[key] = result + self.it_computed[key] = it + + return result + + def statephys_from_statefft(self): + w_fft = self.state_fft['w_fft'] + z_fft = self.state_fft['z_fft'] + self.state_phys['w'] = self.oper.ifft2(w_fft) + self.state_phys['z'] = self.oper.ifft2(z_fft) diff --git a/fluidsim/solvers/plate2d/test/__init__.py b/fluidsim/solvers/plate2d/test/__init__.py new file mode 100644 diff --git a/fluidsim/solvers/plate2d/test/test_solver.py b/fluidsim/solvers/plate2d/test/test_solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9wbGF0ZTJkL3Rlc3QvdGVzdF9zb2x2ZXIucHk= --- /dev/null +++ b/fluidsim/solvers/plate2d/test/test_solver.py @@ -0,0 +1,52 @@ +import unittest +import shutil + +# import numpy as np + +import fluiddyn as fld + +from fluiddyn.io import stdout_redirected + + +class TestSolverPLATE2D(unittest.TestCase): + # @unittest.expectedFailure + def test_tendency(self): + + key_solver = 'PLATE2D' + solver = fld.simul.import_module_solver_from_key(key_solver) + params = fld.simul.create_params(solver) + + params.short_name_type_run = 'test' + + nh = 64 + params.oper.nx = nh + params.oper.ny = nh + Lh = 6. + params.oper.Lx = Lh + params.oper.Ly = Lh + + params.oper.coef_dealiasing = 2./3 + params.nu_8 = 2. + + params.time_stepping.USE_CFL = False + params.time_stepping.deltat0 = 0.005 + params.time_stepping.t_end = 0.5 + + params.init_fields.type_flow_init = 'NOISE' + params.output.HAS_TO_SAVE = False + params.FORCING = False + + params.output.ONLINE_PLOT_OK = False + + with stdout_redirected(): + sim = solver.Simul(params) + + ratio = sim.test_tendencies_nonlin() + + self.assertGreater(1e-15, ratio) + + shutil.rmtree(sim.output.path_run) + + +if __name__ == '__main__': + unittest.main() diff --git a/fluidsim/solvers/sw1l/__init__.py b/fluidsim/solvers/sw1l/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL19faW5pdF9fLnB5 --- /dev/null +++ b/fluidsim/solvers/sw1l/__init__.py @@ -0,0 +1,5 @@ +"""1-layer Shallow-Water solvers (:mod:`fluidsim.solvers.sw1l`) +===================================================================== + + +""" diff --git a/fluidsim/solvers/sw1l/exactlin/__init__.py b/fluidsim/solvers/sw1l/exactlin/__init__.py new file mode 100644 diff --git a/fluidsim/solvers/sw1l/exactlin/solver.py b/fluidsim/solvers/sw1l/exactlin/solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL2V4YWN0bGluL3NvbHZlci5weQ== --- /dev/null +++ b/fluidsim/solvers/sw1l/exactlin/solver.py @@ -0,0 +1,255 @@ +"""SW1l equations solving exactly the linear terms +================================================== + +(:mod:`fluidsim.solvers.sw1l.exactlin.solver`) + + + +""" + +from __future__ import division, print_function + +import numpy as np + + +from fluidsim.operators.setofvariables import SetOfVariables + + +from fluidsim.solvers.sw1l.solver import InfoSolverSW1l +from fluidsim.solvers.sw1l.solver import Simul as SimulSW1l + + +from fluiddyn.util import mpi + + +class InfoSolverSW1lExactLin(InfoSolverSW1l): + """Information about the solver SW1l.""" + def __init__(self, **kargs): + super(InfoSolverSW1lExactLin, self).__init__(**kargs) + + if 'tag' in kargs and kargs['tag'] == 'solver': + + sw1l = 'fluidsim.solvers.sw1l' + + self.module_name = sw1l+'.exactlin.solver' + self.class_name = 'Simul' + self.short_name = 'SW1lexlin' + + classes = self.classes + + classes.State.module_name = sw1l+'.exactlin.state' + classes.State.class_name = 'StateSW1lExactLin' + + classes.InitFields.class_name = 'InitFieldsSW1lExLin' + + classes.Forcing.class_name = 'ForcingSW1lExactLin' + + +info_solver = InfoSolverSW1lExactLin(tag='solver') +info_solver.complete_with_classes() + + +class Simul(SimulSW1l): + """A solver of the shallow-water 1 layer equations (SW1l)""" + + def __init__(self, params, info_solver=info_solver): + super(Simul, self).__init__(params, info_solver) + + # def tendencies_nonlin(self, state_fft=None): + # oper = self.oper + # fft2 = oper.fft2 + + # if state_fft is None: + # state_phys = self.state.state_phys + # state_fft = self.state.state_fft + # else: + # state_phys = self.return_statephys_from_statefft(state_fft) + + # ux = state_phys['ux'] + # uy = state_phys['uy'] + # eta = state_phys['eta'] + # q = state_phys['q'] + # d = state_phys['div'] + + # q_fft = state_fft['q_fft'] + # ap_fft = state_fft['ap_fft'] + # am_fft = state_fft['am_fft'] + + # a_fft = ap_fft + am_fft + # div_fft = self.divfft_from_apamfft(ap_fft, am_fft) + + # pxq_fft, pyq_fft = oper.gradfft_from_fft(q_fft) + # pxq = oper.ifft2(pxq_fft) + # pyq = oper.ifft2(pyq_fft) + + # Fq = -ux*pxq - uy*pyq - q*d + # Fq_fft = fft2(Fq) + + def tendencies_nonlin(self, state_fft=None): + oper = self.oper + fft2 = oper.fft2 + + if state_fft is None: + state_phys = self.state.state_phys + state_fft = self.state.state_fft + else: + state_phys = self.state.return_statephys_from_statefft(state_fft) + + ux = state_phys['ux'] + uy = state_phys['uy'] + eta = state_phys['eta'] + rot = state_phys['rot'] + + # compute the nonlinear terms for ux, uy and eta + N1x = +rot*uy + N1y = -rot*ux + gradu2_x_fft, gradu2_y_fft = oper.gradfft_from_fft( + fft2(ux**2+uy**2)/2) + + Nx_fft = fft2(N1x) - gradu2_x_fft + Ny_fft = fft2(N1y) - gradu2_y_fft + + jx_fft = fft2(eta*ux) + jy_fft = fft2(eta*uy) + Neta_fft = -oper.divfft_from_vecfft(jx_fft, jy_fft) + + # self.verify_tendencies(state_fft, state_phys, + # Nx_fft, Ny_fft, Neta_fft) + + # compute the nonlinear terms for q, ap and am + (Nq_fft, Np_fft, Nm_fft + ) = self.oper.qapamfft_from_uxuyetafft(Nx_fft, Ny_fft, Neta_fft) + + # Nq_fft = self.oper.constant_arrayK(value=0) + # Np_fft = self.oper.constant_arrayK(value=0) + # Nm_fft = self.oper.constant_arrayK(value=0) + + oper.dealiasing(Nq_fft, Np_fft, Nm_fft) + + tendencies_fft = SetOfVariables( + like_this_sov=self.state.state_fft, + name_type_variables='tendencies_nonlin') + tendencies_fft['q_fft'] = Nq_fft + tendencies_fft['ap_fft'] = Np_fft + tendencies_fft['am_fft'] = Nm_fft + + if self.params.FORCING: + tendencies_fft += self.forcing.get_forcing() + + return tendencies_fft + + def compute_freq_complex(self, key): + K2 = self.oper.K2 + if key == 'q_fft': + omega = self.oper.constant_arrayK(value=0) + elif key == 'ap_fft': + omega = 1.j*np.sqrt(self.params.f**2 + self.params.c2*K2) + elif key == 'am_fft': + omega = -1.j*np.sqrt(self.params.f**2 + self.params.c2*K2) + return omega + + def verify_tendencies(self, state_fft, state_phys, + Nx_fft, Ny_fft, Neta_fft): + # for verification conservation energy + # compute the linear terms + oper = self.oper + ux = state_phys['ux'] + uy = state_phys['uy'] + eta = state_phys['eta'] + + q_fft = state_fft['q_fft'] + ap_fft = state_fft['ap_fft'] + am_fft = state_fft['am_fft'] + a_fft = ap_fft + am_fft + div_fft = self.divfft_from_apamfft(ap_fft, am_fft) + + eta_fft = (oper.etafft_from_qfft(q_fft) + + oper.etafft_from_afft(a_fft)) + + dx_c2eta_fft, dy_c2eta_fft = oper.gradfft_from_fft( + self.params.c2*eta_fft) + LCx = self.params.f*uy + LCy = -self.params.f*ux + Lx_fft = oper.fft2(LCx) - dx_c2eta_fft + Ly_fft = oper.fft2(LCy) - dy_c2eta_fft + Leta_fft = -div_fft + + # compute the full tendencies + Fx_fft = Lx_fft + Nx_fft + Fy_fft = Ly_fft + Ny_fft + Feta_fft = Leta_fft + Neta_fft + oper.dealiasing(Fx_fft, Fy_fft, Feta_fft) + + # test : ux, uy, eta ---> q, ap, am + (Fq_fft, Fp_fft, Fm_fft + ) = self.oper.qapamfft_from_uxuyetafft(Fx_fft, Fy_fft, Feta_fft) + # test : q, ap, am ---> ux, uy, eta + (Fx2_fft, Fy2_fft, Feta2_fft + ) = self.oper.uxuyetafft_from_qapamfft(Fq_fft, Fp_fft, Fm_fft) + print(np.max(abs(Fx2_fft - Fx_fft))) + print(np.max(abs(Fy2_fft - Fy_fft))) + print(np.max(abs(Feta2_fft - Feta_fft))) + Fx_fft = Fx2_fft + Fy_fft = Fy2_fft + Feta_fft = Feta2_fft + + (Fq2_fft, Fp2_fft, Fm2_fft + ) = self.oper.qapamfft_from_uxuyetafft( + Fx2_fft, Fy2_fft, Feta2_fft) + print(np.max(abs(Fq2_fft - Fq_fft))) + print(np.max(abs(Fp2_fft - Fp_fft))) + print(np.max(abs(Fm2_fft - Fm_fft))) + + Fx = oper.ifft2(Fx_fft) + Fy = oper.ifft2(Fy_fft) + Feta = oper.ifft2(Feta_fft) + A = (Feta*(ux**2+uy**2)/2 + + (1+eta)*(ux*Fx+uy*Fy) + + self.params.c2*eta*Feta) + A_fft = oper.fft2(A) + if mpi.rank == 0: + print('should be zero =', A_fft[0, 0]) + + +if __name__ == "__main__": + + import fluiddyn as fld + + params = fld.simul.create_params(info_solver) + + params.short_name_type_run = 'test' + + nh = 64 + Lh = 2*np.pi + params.oper.nx = nh + params.oper.ny = nh + params.oper.Lx = Lh + params.oper.Ly = Lh + + delta_x = params.oper.Lx/params.oper.nx + params.nu_8 = 2.*10e-1*params.forcing.forcing_rate**(1./3)*delta_x**8 + + params.time_stepping.t_end = 2. + + params.init_fields.type_flow_init = 'NOISE' + + params.output.periods_print.print_stdout = 0.25 + + params.output.periods_save.phys_fields = 1. + params.output.periods_save.spectra = 0.5 + params.output.periods_save.spect_energy_budg = 0.5 + params.output.periods_save.increments = 0.5 + params.output.periods_save.pdf = 0.5 + params.output.periods_save.time_signals_fft = False + + params.output.periods_plot.phys_fields = 0. + + params.output.phys_fields.field_to_plot = 'div' + + sim = Simul(params) + + # sim.output.phys_fields.plot() + sim.time_stepping.start() + # sim.output.phys_fields.plot() + + fld.show() diff --git a/fluidsim/solvers/sw1l/exactlin/state.py b/fluidsim/solvers/sw1l/exactlin/state.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL2V4YWN0bGluL3N0YXRlLnB5 --- /dev/null +++ b/fluidsim/solvers/sw1l/exactlin/state.py @@ -0,0 +1,193 @@ +"""State class for the SW1l.exactlin solver +(:mod:`fluidsim.solvers.sw1l.exactlin.state`) +=================================================== + +.. currentmodule:: fluidsim.solvers.sw1l.exactlin.state + +Provides: + +.. autoclass:: StateSW1lExactLin + :members: + :private-members: + +""" + +from fluidsim.operators.setofvariables import SetOfVariables + +from fluidsim.solvers.sw1l.state import StateSW1l + +from fluiddyn.util import mpi + + +class StateSW1lExactLin(StateSW1l): + """ + The class :class:`StateSW1lexlin` contains the variables corresponding + to the state and handles the access to other fields for the solver + SW1l. + """ + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + info_solver.classes.State.set_attribs({ + 'keys_state_fft': ['ap_fft', 'am_fft', 'q_fft'], + 'keys_state_phys': ['ux', 'uy', 'eta', 'rot'], + 'keys_computable': [], + 'keys_phys_needed': ['ux', 'uy', 'eta'], + 'keys_linear_eigenmodes': ['q_fft', 'a_fft', 'd_fft']}) + + + + def compute(self, key, SAVE_IN_DICT=True, RAISE_ERROR=True): + it = self.sim.time_stepping.it + + if (key in self.vars_computed and it == self.it_computed[key]): + return self.vars_computed[key] + + if key == 'div_fft': + ap_fft = self.state_fft['ap_fft'] + am_fft = self.state_fft['am_fft'] + d_fft = self.oper.divfft_from_apamfft(ap_fft, am_fft) + result = d_fft + + elif key == 'a_fft': + ap_fft = self.state_fft['ap_fft'] + am_fft = self.state_fft['am_fft'] + result = ap_fft + am_fft + + elif key == 'rot_fft': + q_fft = self.state_fft['q_fft'] + a_fft = self.compute('a_fft') + result = (self.oper.rotfft_from_qfft(q_fft) + + self.oper.rotfft_from_afft(a_fft) + ) + + elif key == 'eta_fft': + q_fft = self.state_fft['q_fft'] + a_fft = self.compute('a_fft') + result = (self.oper.etafft_from_qfft(q_fft) + + self.oper.etafft_from_afft(a_fft) + ) + + + + + + + + elif key == 'ux_fft': + rot_fft = self.compute('rot_fft') + div_fft = self.compute('div_fft') + urx_fft, ury_fft = self.oper.vecfft_from_rotfft(rot_fft) + udx_fft, udy_fft = self.oper.vecfft_from_divfft(div_fft) + ux_fft = urx_fft + udx_fft + if mpi.rank == 0: + ap_fft = self.state_fft['ap_fft'] + ux_fft[0, 0] = ap_fft[0, 0] + result = ux_fft + if SAVE_IN_DICT: + key2 = 'uy_fft' + uy_fft = ury_fft + udy_fft + if mpi.rank == 0: + am_fft = self.state_fft['am_fft'] + uy_fft[0, 0] = am_fft[0, 0] + + self.vars_computed[key2] = uy_fft + self.it_computed[key2] = it + + elif key == 'uy_fft': + rot_fft = self.compute('rot_fft') + div_fft = self.compute('div_fft') + urx_fft, ury_fft = self.oper.vecfft_from_rotfft(rot_fft) + udx_fft, udy_fft = self.oper.vecfft_from_divfft(div_fft) + uy_fft = ury_fft + udy_fft + if mpi.rank == 0: + am_fft = self.state_fft['am_fft'] + uy_fft[0, 0] = am_fft[0, 0] + result = uy_fft + if SAVE_IN_DICT: + key2 = 'ux_fft' + ux_fft = urx_fft + udx_fft + if mpi.rank == 0: + ap_fft = self.state_fft['ap_fft'] + ux_fft[0, 0] = ap_fft[0, 0] + self.vars_computed[key2] = ux_fft + self.it_computed[key2] = it + + else: + result = super(StateSW1lExactLin, self).compute( + key, SAVE_IN_DICT=SAVE_IN_DICT, + RAISE_ERROR=RAISE_ERROR) + SAVE_IN_DICT = False + + if SAVE_IN_DICT: + self.vars_computed[key] = result + self.it_computed[key] = it + + return result + + + + + + def statefft_from_statephys(self): + """Compute the state in Fourier space.""" + ux = self.state_phys['ux'] + uy = self.state_phys['uy'] + eta = self.state_phys['eta'] + + eta_fft = self.oper.fft2(eta) + ux_fft = self.oper.fft2(ux) + uy_fft = self.oper.fft2(uy) + + (q_fft, ap_fft, am_fft + ) = self.oper.qapamfft_from_uxuyetafft(ux_fft, uy_fft, eta_fft) + + self.state_fft['q_fft'] = q_fft + self.state_fft['ap_fft'] = ap_fft + self.state_fft['am_fft'] = am_fft + + + + def statephys_from_statefft(self): + """Compute the state in physical space.""" + ifft2 = self.oper.ifft2 + q_fft = self.state_fft['q_fft'] + ap_fft = self.state_fft['ap_fft'] + am_fft = self.state_fft['am_fft'] + + (ux_fft, uy_fft, eta_fft + ) = self.oper.uxuyetafft_from_qapamfft(q_fft, ap_fft, am_fft) + + rot_fft = q_fft + self.params.f*eta_fft + + self.state_phys['ux'] = ifft2(ux_fft) + self.state_phys['uy'] = ifft2(uy_fft) + self.state_phys['eta'] = ifft2(eta_fft) + self.state_phys['rot'] = ifft2(rot_fft) + + + def return_statephys_from_statefft(self, state_fft=None): + """Return the state in physical space.""" + ifft2 = self.oper.ifft2 + if state_fft is None: + state_fft = self.state_fft + + q_fft = state_fft['q_fft'] + ap_fft = state_fft['ap_fft'] + am_fft = state_fft['am_fft'] + + (ux_fft, uy_fft, eta_fft + ) = self.oper.uxuyetafft_from_qapamfft(q_fft, ap_fft, am_fft) + + rot_fft = q_fft + self.params.f*eta_fft + + state_phys = SetOfVariables(like_this_sov=self.state_phys) + state_phys['ux'] = ifft2(ux_fft) + state_phys['uy'] = ifft2(uy_fft) + state_phys['eta'] = ifft2(eta_fft) + state_phys['rot'] = ifft2(rot_fft) + return state_phys diff --git a/fluidsim/solvers/sw1l/forcing.py b/fluidsim/solvers/sw1l/forcing.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL2ZvcmNpbmcucHk= --- /dev/null +++ b/fluidsim/solvers/sw1l/forcing.py @@ -0,0 +1,497 @@ + + +import numpy as np + +from fluiddyn.util import mpi + +from fluidsim.base.forcing import ForcingBasePseudoSpectral + +from fluidsim.base.forcing.specific import \ + Proportional as ProportionalBase + +from fluidsim.base.forcing.specific import \ + TimeCorrelatedRandomPseudoSpectral as TCRandomPS + + +class ForcingSW1l(ForcingBasePseudoSpectral): + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + ForcingBasePseudoSpectral._complete_info_solver(info_solver) + classes = info_solver.classes.Forcing.classes + + package = 'fluidsim.solvers.plate2d.forcing' + + classes.set_child( + 'Random', + attribs={'module_name': package, + 'class_name': 'Random'}) + + classes.set_child( + 'Proportional', + attribs={'module_name': package, + 'class_name': 'Proportional'}) + + +class Random(TCRandomPS): + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + TCRandomPS._complete_params_with_default(params) + params.forcing.key_forced = 'q_fft' + + +class Proportional(ProportionalBase): + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + params.forcing.key_forced = 'q_fft' + + +class TimeCorrelatedRandomPseudoSpectralGauss(TCRandomPS): + def compute_forcingc_raw(self): + Fq_fft = super(TCRandomPS, self).compute_forcingc_raw() + + return Fq_fft + + + + + # def compute_forcing_proportional(self): + # """Compute a forcing proportional to the flow.""" + # shapeK_loc_c = self.shapeK_loc_coarse + # q_fft = self.qfftcoarse_from_setvarfft() + + # if mpi.rank > 0: + # Fq_fft = np.empty(shapeK_loc_c, + # dtype=np.complex128) + # else: + # Fq_fft = self.normalize_forcingc_proportional(q_fft) + # # self.verify_injection_rate_opfft(q_fft, Fq_fft, + # # self.oper_coarse) + # self.fill_forcingc_from_Fqfft(Fq_fft) + + # self.put_forcingc_in_forcing() + # ## verification + # self.verify_injection_rate_from_state() + + + + # def compute_forcing_2nd_degree_eq(self): + # """compute a forcing normalized with a 2nd degree eq.""" + # shapeK_loc_c = self.shapeK_loc_coarse + # q_fft = self.qfftcoarse_from_setvarfft() + + # if mpi.rank > 0: + # Fq_fft = np.empty(shapeK_loc_c, + # dtype=np.complex128) + # else: + # Fq_fft = self.forcingc_raw_each_time() + # Fq_fft = self.normalize_forcingc_2nd_degree_eq(Fq_fft, + # q_fft) + # # self.verify_injection_rate_opfft(q_fft, Fq_fft, + # # self.oper_coarse) + # self.fill_forcingc_from_Fqfft(Fq_fft) + + # self.put_forcingc_in_forcing() + # ## verification + # # self.verify_injection_rate_from_state() + + +class OldStuff(object): + + def compute_forcing_waves(self): + """compute a forcing normalized with a 2nd degree eq.""" + shapeK_loc_c = self.shapeK_loc_coarse + a_fft, eta_fft = self.aetafftcoarse_from_setvarfft() + if mpi.rank > 0: + Fa_fft = np.empty(shapeK_loc_c, + dtype=np.complex128) + else: + Fa_fft = self.forcingc_random() + self.modify_Ffft_from_eta(Fa_fft, eta_fft) + + if np.max(abs(Fa_fft)) > 0: + self.normalize_Fafft_constPquadE(Fa_fft, + a_fft) + + self.fill_forcingc_from_Fafft(Fa_fft) + + self.put_forcingc_in_forcing() + + + + + + + + + + def compute_forcing_particular_k(self): + """compute a forcing "decorralated" from the flow""" + + shapeK_loc_c = self.shapeK_loc_coarse + q_fft = self.qfftcoarse_from_setvarfft() + + if mpi.rank > 0: + Fq_fft = np.empty(shapeK_loc_c, + dtype=np.complex128) + else: + Fq_fft = self.forcingc_raw_each_time() + Fq_fft = self.normalize_forcingc_part_k(Fq_fft, + q_fft) + # self.verify_injection_rate_opfft(q_fft, Fq_fft, + # self.oper_coarse) + self.fill_forcingc_from_Fqfft(Fq_fft) + + self.put_forcingc_in_forcing() + ## verification + self.verify_injection_rate_from_state() + + + + + def verify_injection_rate_opfft(self, q_fft, Fq_fft, oper): + """Verify injection rate.""" + P_Z_forcing1 = abs(Fq_fft)**2/2*self.sim.time_stepping.deltat + P_Z_forcing2 = np.real(Fq_fft.conj()*q_fft) + P_Z_forcing1 = oper.sum_wavenumbers(P_Z_forcing1) + P_Z_forcing2 = oper.sum_wavenumbers(P_Z_forcing2) + if mpi.rank==0: + print 'P_Z_f = {0:9.4e} ; P_Z_f2 = {1:9.4e};'.format( + P_Z_forcing1+P_Z_forcing2, + P_Z_forcing2) + + + + + + + + + + + def verify_injection_rate_from_state(self): + """Verify injection rate.""" + + ux_fft = self.sim.state.state_fft['ux_fft'] + uy_fft = self.sim.state.state_fft['uy_fft'] + eta_fft = self.sim.state.state_fft['eta_fft'] + + q_fft, div_fft, ageo_fft = \ + self.oper.qdafft_from_uxuyetafft(ux_fft, uy_fft, eta_fft) + + Fux_fft = self.forcing_fft['ux_fft'] + Fuy_fft = self.forcing_fft['uy_fft'] + Feta_fft = self.forcing_fft['eta_fft'] + + Fq_fft, Fdiv_fft, Fageo_fft = \ + self.oper.qdafft_from_uxuyetafft(Fux_fft, Fuy_fft, Feta_fft) + # print 'Fq_fft', abs(Fq_fft).max() + # print 'Fdiv_fft', abs(Fdiv_fft).max() + # print 'Fageo_fft', abs(Fageo_fft).max() + + self.verify_injection_rate_opfft(q_fft, Fq_fft, self.oper) + + + + + + + + def qfftcoarse_from_setvarfft(self, set_var_fft=None): + if set_var_fft is None: + set_var_fft = self.sim.state.state_fft + ux_fft = set_var_fft['ux_fft'] + uy_fft = set_var_fft['uy_fft'] + eta_fft = set_var_fft['eta_fft'] + shapeK_loc_c = self.shapeK_loc_coarse + ux_fft = self.oper.coarse_seq_from_fft_loc(ux_fft, shapeK_loc_c) + uy_fft = self.oper.coarse_seq_from_fft_loc(uy_fft, shapeK_loc_c) + eta_fft = self.oper.coarse_seq_from_fft_loc(eta_fft, shapeK_loc_c) + if mpi.rank > 0: + q_fft = np.empty(shapeK_loc_c, + dtype=np.complex128) + else: + rot_fft = self.oper_coarse.rotfft_from_vecfft(ux_fft, uy_fft) + q_fft = rot_fft-self.params.f*eta_fft + return q_fft + + + + + + def etafftcoarse_from_setvarfft(self, set_var_fft=None): + if set_var_fft is None: + set_var_fft = self.sim.state.state_fft + eta_fft = set_var_fft['eta_fft'] + shapeK_loc_c = self.shapeK_loc_coarse + eta_fft = self.oper.coarse_seq_from_fft_loc(eta_fft, shapeK_loc_c) + return eta_fft + + + + def aetafftcoarse_from_setvarfft(self, set_var_fft=None): + if set_var_fft is None: + set_var_fft = self.sim.state.state_fft + eta_fft = set_var_fft['eta_fft'] + ux_fft = set_var_fft['ux_fft'] + uy_fft = set_var_fft['uy_fft'] + shapeK_loc_c = self.shapeK_loc_coarse + eta_fft = self.oper.coarse_seq_from_fft_loc(eta_fft, shapeK_loc_c) + ux_fft = self.oper.coarse_seq_from_fft_loc(ux_fft, shapeK_loc_c) + uy_fft = self.oper.coarse_seq_from_fft_loc(uy_fft, shapeK_loc_c) + + if mpi.rank > 0: + a_fft = np.empty(shapeK_loc_c, + dtype=np.complex128) + else: + a_fft = self.oper_coarse.afft_from_uxuyetafft(ux_fft, uy_fft, + eta_fft) + + return a_fft, eta_fft + + + def modify_Ffft_from_eta(self, F_fft, eta_fft): + """Put to zero the forcing for the too large modes.""" + for ik in self.ind_forcing: + if abs(eta_fft.flat[ik]) > self.eta_cond: + F_fft.flat[ik] = 0. + + + + + def fill_forcingc_from_Fqfft(self, Fq_fft): + + Fux_fft, Fuy_fft, Feta_fft = \ + self.oper_coarse.uxuyetafft_from_qfft(Fq_fft) + self.forcingc_fft['ux_fft'] = Fux_fft + self.forcingc_fft['uy_fft'] = Fuy_fft + self.forcingc_fft['eta_fft'] = Feta_fft + + + def fill_forcingc_from_Fetafft(self, Feta_fft): + + self.forcingc_fft['ux_fft'] = \ + self.oper_coarse.constant_arrayK(value=0.) + self.forcingc_fft['uy_fft'] = \ + self.oper_coarse.constant_arrayK(value=0.) + self.forcingc_fft['eta_fft'] = Feta_fft + + + def fill_forcingc_from_Fafft(self, Fa_fft): + + Fux_fft, Fuy_fft, Feta_fft = \ + self.oper_coarse.uxuyetafft_from_afft(Fa_fft) + self.forcingc_fft['ux_fft'] = Fux_fft + self.forcingc_fft['uy_fft'] = Fuy_fft + self.forcingc_fft['eta_fft'] = Feta_fft + + + + + + def get_FxFyFetafft(self): + + Fx_fft = self.forcing_fft['ux_fft'] + Fy_fft = self.forcing_fft['uy_fft'] + Feta_fft = self.forcing_fft['eta_fft'] + return Fx_fft, Fy_fft, Feta_fft + + + + def normalize_Fafft_constPquadE(self, Fa_fft, a_fft): + """Normalize the forcing Fa_fft such as the forcing rate of + quadratic energy is equal to self.forcing_rate.""" + oper_c = self.oper_coarse + params = self.params + deltat = self.sim.time_stepping.deltat + + Fux_fft, Fuy_fft, Feta_fft = \ + oper_c.uxuyetafft_from_afft(Fa_fft) + ux_fft, uy_fft, eta_fft = \ + oper_c.uxuyetafft_from_afft(a_fft) + + ax = deltat/2*oper_c.sum_wavenumbers(abs(Fux_fft)**2) + ay = deltat/2*oper_c.sum_wavenumbers(abs(Fuy_fft)**2) + aA = params.c2*deltat/2*oper_c.sum_wavenumbers(abs(Feta_fft)**2) + a = ax + ay + aA + + bx = oper_c.sum_wavenumbers( + (ux_fft.conj()*Fux_fft).real) + by = oper_c.sum_wavenumbers( + (uy_fft.conj()*Fuy_fft).real) + bA = params.c2*oper_c.sum_wavenumbers( + (eta_fft.conj()*Feta_fft).real) + b = bx + by + bA + + c = -self.forcing_rate + + Delta = b**2 - 4*a*c + alpha = (np.sqrt(Delta) - b)/(2*a) + + Fa_fft[:] = alpha*Fa_fft + + + + + +class ForcingSW1lExactLin(ForcingSW1l): + + + def verify_injection_rate_from_state(self): + """Verify injection rate.""" + + q_fft = self.sim.state.state_fft['q_fft'] + Fq_fft = self.forcing_fft['q_fft'] + # print 'Fq_fft', abs(Fq_fft).max() + self.verify_injection_rate_opfft(q_fft, Fq_fft, self.oper) + + def qfftcoarse_from_setvarfft(self, set_var_fft=None): + if set_var_fft is None: + set_var_fft = self.sim.state.state_fft + q_fft = set_var_fft['q_fft'] + shapeK_loc_c = self.shapeK_loc_coarse + q_fft = self.oper.coarse_seq_from_fft_loc(q_fft, shapeK_loc_c) + return q_fft + + def fill_forcingc_from_Fqfft(self, Fq_fft): + + self.forcingc_fft['q_fft'] = Fq_fft + self.forcingc_fft['ap_fft'] = \ + self.oper_coarse.constant_arrayK(value=0.) + self.forcingc_fft['am_fft'] = \ + self.oper_coarse.constant_arrayK(value=0.) + + + def get_FxFyFetafft(self): + + Fq_fft = self.forcing_fft['q_fft'] + Fp_fft = self.forcing_fft['ap_fft'] + Fm_fft = self.forcing_fft['am_fft'] + + return self.oper.uxuyetafft_from_qapamfft(Fq_fft, Fp_fft, Fm_fft) + + + + + + def etafftcoarse_from_setvarfft(self, set_var_fft=None): + if set_var_fft is None: + set_var_fft = self.sim.state.state_fft + q_fft = set_var_fft['q_fft'] + ap_fft = set_var_fft['ap_fft'] + am_fft = set_var_fft['am_fft'] + a_fft = ap_fft + am_fft + eta_fft = (self.oper_coarse.etafft_from_qfft(q_fft) + + self.oper_coarse.etafft_from_afft(a_fft) + ) + shapeK_loc_c = self.shapeK_loc_coarse + eta_fft = self.oper.coarse_seq_from_fft_loc(eta_fft, shapeK_loc_c) + return eta_fft + + + + def fill_forcingc_from_Fafft(self, Fa_fft): + + self.forcingc_fft['q_fft'] = \ + self.oper_coarse.constant_arrayK(value=0.) + self.forcingc_fft['ap_fft'] = 0.5*Fa_fft + self.forcingc_fft['am_fft'] = 0.5*Fa_fft + + + def aetafftcoarse_from_setvarfft(self, set_var_fft=None): + if set_var_fft is None: + set_var_fft = self.sim.state.state_fft + q_fft = set_var_fft['q_fft'] + ap_fft = set_var_fft['ap_fft'] + am_fft = set_var_fft['am_fft'] + shapeK_loc_c = self.shapeK_loc_coarse + q_fft = self.oper.coarse_seq_from_fft_loc(q_fft, shapeK_loc_c) + ap_fft = self.oper.coarse_seq_from_fft_loc(ap_fft, shapeK_loc_c) + am_fft = self.oper.coarse_seq_from_fft_loc(am_fft, shapeK_loc_c) + + if mpi.rank > 0: + a_fft = np.empty(shapeK_loc_c, + dtype=np.complex128) + eta_fft = np.empty(shapeK_loc_c, + dtype=np.complex128) + else: + a_fft = ap_fft + am_fft + eta_fft = self.oper_coarse.etafft_from_aqfft(a_fft, q_fft) + + return a_fft, eta_fft + + + + + + + + +class ForcingSW1lWaves(ForcingSW1l): + + + + + def qfftcoarse_from_setvarfft(self, set_var_fft=None): + raise ValueError('This solver does not solve for q.') + + + def get_FxFyFetafft(self): + + Fq_fft = self.oper.constant_arrayK(value=0) + Fp_fft = self.forcing_fft['ap_fft'] + Fm_fft = self.forcing_fft['am_fft'] + + return self.oper.uxuyetafft_from_qapamfft(Fq_fft, Fp_fft, Fm_fft) + + + + + + def etafftcoarse_from_setvarfft(self, set_var_fft=None): + if set_var_fft is None: + set_var_fft = self.sim.state.state_fft + ap_fft = set_var_fft['ap_fft'] + am_fft = set_var_fft['am_fft'] + a_fft = ap_fft + am_fft + eta_fft = self.oper_coarse.etafft_from_afft(a_fft) + shapeK_loc_c = self.shapeK_loc_coarse + eta_fft = self.oper.coarse_seq_from_fft_loc(eta_fft, shapeK_loc_c) + return eta_fft + + + + def fill_forcingc_from_Fafft(self, Fa_fft): + + self.forcingc_fft['ap_fft'] = 0.5*Fa_fft + self.forcingc_fft['am_fft'] = 0.5*Fa_fft + + + def aetafftcoarse_from_setvarfft(self, set_var_fft=None): + if set_var_fft is None: + set_var_fft = self.sim.state.state_fft + ap_fft = set_var_fft['ap_fft'] + am_fft = set_var_fft['am_fft'] + shapeK_loc_c = self.shapeK_loc_coarse + ap_fft = self.oper.coarse_seq_from_fft_loc(ap_fft, shapeK_loc_c) + am_fft = self.oper.coarse_seq_from_fft_loc(am_fft, shapeK_loc_c) + + if mpi.rank > 0: + a_fft = np.empty(shapeK_loc_c, + dtype=np.complex128) + eta_fft = np.empty(shapeK_loc_c, + dtype=np.complex128) + else: + a_fft = ap_fft + am_fft + q_fft = self.oper_coarse.constant_arrayK(value=0) + eta_fft = self.oper_coarse.etafft_from_aqfft(a_fft, q_fft) + + return a_fft, eta_fft diff --git a/fluidsim/solvers/sw1l/init_fields.py b/fluidsim/solvers/sw1l/init_fields.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL2luaXRfZmllbGRzLnB5 --- /dev/null +++ b/fluidsim/solvers/sw1l/init_fields.py @@ -0,0 +1,213 @@ + +"""InitFieldsSW1l""" + +import os + +from fluiddyn.util import mpi + +from fluidsim.base.init_fields import InitFieldsBase + + +class InitFieldsSW1l(InitFieldsBase): + """Init the fields for the solver SW1l.""" + + implemented_flows = ['NOISE', 'CONSTANT', 'LOAD_FILE', + 'DIPOLE', 'JET', 'WAVE'] + + def __call__(self): + """Initialization initial fields""" + sim = self.sim + + sim.time_stepping.t = 0. + sim.time_stepping.it = 0 + oper = sim.oper + + type_flow_init = self.get_and_check_type_flow_init() + + if type_flow_init == 'DIPOLE': + rot_fft, ux_fft, uy_fft = self.init_fields_1dipole() + self.fill_state_from_uxuyfft(ux_fft, uy_fft) + elif type_flow_init == 'JET': + rot_fft, ux_fft, uy_fft = self.init_fields_jet() + self.fill_state_from_uxuyfft(ux_fft, uy_fft) + elif type_flow_init == 'NOISE': + rot_fft, ux_fft, uy_fft = self.init_fields_noise() + self.fill_state_from_uxuyfft(ux_fft, uy_fft) + elif type_flow_init == 'LOAD_FILE': + path_file = sim.params.init_fields.path_file + if not os.path.exists(path_file): + raise ValueError('file \"{0}\" not found'.format(path_file)) + self.get_state_from_file(path_file) + elif type_flow_init == 'CONSTANT': + ux_fft = oper.constant_arrayK(value=1.) + uy_fft = oper.constant_arrayK(value=0.) + self.fill_state_from_uxuyfft(ux_fft, uy_fft) + elif type_flow_init == 'WAVE': + eta_fft, ux_fft, uy_fft = \ + self.init_fields_wave() + self.fill_state_from_uxuyetafft(ux_fft, uy_fft, eta_fft) + else: + raise ValueError('bad value of params.init_fields.type_flow_init') + + def fill_state_from_uxuyetafft(self, ux_fft, uy_fft, eta_fft): + sim = self.sim + state_fft = sim.state.state_fft + state_fft['ux_fft'] = ux_fft + state_fft['uy_fft'] = uy_fft + state_fft['eta_fft'] = eta_fft + + sim.oper.dealiasing(state_fft) + sim.state.statephys_from_statefft() + + def fill_state_from_uxuyfft(self, ux_fft, uy_fft): + sim = self.sim + oper = sim.oper + ifft2 = oper.ifft2 + + oper.projection_perp(ux_fft, uy_fft) + oper.dealiasing(ux_fft, uy_fft) + + ux = ifft2(ux_fft) + uy = ifft2(uy_fft) + + rot_fft = oper.rotfft_from_vecfft(ux_fft, uy_fft) + rot = ifft2(rot_fft) + + eta_fft = self.etafft_no_div(ux, uy, rot) + eta = ifft2(eta_fft) + + state_fft = sim.state.state_fft + state_fft['ux_fft'] = ux_fft + state_fft['uy_fft'] = uy_fft + state_fft['eta_fft'] = eta_fft + + state_phys = sim.state.state_phys + state_phys['rot'] = rot + state_phys['ux'] = ux + state_phys['uy'] = uy + state_phys['eta'] = eta + + def etafft_no_div(self, ux, uy, rot): + K2_not0 = self.oper.K2_not0 + rot_abs = rot + self.params.f + + tempx_fft = -self.oper.fft2(rot_abs*uy) + tempy_fft = +self.oper.fft2(rot_abs*ux) + + uu2_fft = self.oper.fft2(ux**2+uy**2) + + eta_fft = (1.j * self.oper.KX*tempx_fft/K2_not0 + + 1.j*self.oper.KY*tempy_fft/K2_not0 - + uu2_fft/2)/self.params.c2 + if mpi.rank == 0: + eta_fft[0, 0] = 0. + self.oper.dealiasing(eta_fft) + + return eta_fft + + +class InitFieldsSW1lExLin(InitFieldsSW1l): + """Init the fields for the solver SW1lExLin.""" + + def fill_state_from_uxuyetafft(self, ux_fft, uy_fft, eta_fft): + sim = self.sim + + (q_fft, ap_fft, am_fft + ) = self.oper.qapamfft_from_uxuyetafft(ux_fft, uy_fft, eta_fft) + + # q_fft = self.oper.constant_arrayK(value=0) + # ap_fft = self.oper.constant_arrayK(value=0) + # am_fft = self.oper.constant_arrayK(value=0) + + # am_fft[0,8] = 1. + + state_fft = sim.state.state_fft + state_fft['q_fft'] = q_fft + state_fft['ap_fft'] = ap_fft + state_fft['am_fft'] = am_fft + + sim.oper.dealiasing(state_fft) + sim.state.statephys_from_statefft() + + def fill_state_from_uxuyfft(self, ux_fft, uy_fft): + sim = self.sim + oper = sim.oper + ifft2 = oper.ifft2 + + oper.projection_perp(ux_fft, uy_fft) + oper.dealiasing(ux_fft, uy_fft) + + ux = ifft2(ux_fft) + uy = ifft2(uy_fft) + + rot_fft = oper.rotfft_from_vecfft(ux_fft, uy_fft) + rot = ifft2(rot_fft) + + eta_fft = self.etafft_no_div(ux, uy, rot) + eta = ifft2(eta_fft) + + (q_fft, ap_fft, am_fft + ) = self.oper.qapamfft_from_uxuyetafft(ux_fft, uy_fft, eta_fft) + + state_fft = sim.state.state_fft + state_fft['q_fft'] = q_fft + state_fft['ap_fft'] = ap_fft + state_fft['am_fft'] = am_fft + + state_phys = sim.state.state_phys + state_phys['rot'] = rot + state_phys['ux'] = ux + state_phys['uy'] = uy + state_phys['eta'] = eta + + +class InitFieldsSW1lWaves(InitFieldsSW1l): + """Init """ + + def fill_state_from_uxuyetafft(self, ux_fft, uy_fft, eta_fft): + sim = self.sim + + (q_fft, ap_fft, am_fft + ) = self.oper.qapamfft_from_uxuyetafft(ux_fft, uy_fft, eta_fft) + + # q_fft = self.oper.constant_arrayK(value=0) + # ap_fft = self.oper.constant_arrayK(value=0) + # am_fft = self.oper.constant_arrayK(value=0) + + # am_fft[0,8] = 1. + + state_fft = sim.state.state_fft + state_fft['ap_fft'] = ap_fft + state_fft['am_fft'] = am_fft + + sim.oper.dealiasing(state_fft) + sim.state.statephys_from_statefft() + + def fill_state_from_uxuyfft(self, ux_fft, uy_fft): + sim = self.sim + oper = sim.oper + ifft2 = oper.ifft2 + + oper.projection_perp(ux_fft, uy_fft) + oper.dealiasing(ux_fft, uy_fft) + + ux = ifft2(ux_fft) + uy = ifft2(uy_fft) + + rot_fft = oper.rotfft_from_vecfft(ux_fft, uy_fft) + rot = ifft2(rot_fft) + + eta_fft = self.etafft_no_div(ux, uy, rot) + eta = ifft2(eta_fft) + + (q_fft, ap_fft, am_fft + ) = self.oper.qapamfft_from_uxuyetafft(ux_fft, uy_fft, eta_fft) + + state_fft = sim.state.state_fft + state_fft['ap_fft'] = ap_fft + state_fft['am_fft'] = am_fft + + state_phys = sim.state.state_phys + state_phys['ux'] = ux + state_phys['uy'] = uy + state_phys['eta'] = eta diff --git a/fluidsim/solvers/sw1l/modified/__init__.py b/fluidsim/solvers/sw1l/modified/__init__.py new file mode 100644 diff --git a/fluidsim/solvers/sw1l/modified/init_fields.py b/fluidsim/solvers/sw1l/modified/init_fields.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL21vZGlmaWVkL2luaXRfZmllbGRzLnB5 --- /dev/null +++ b/fluidsim/solvers/sw1l/modified/init_fields.py @@ -0,0 +1,39 @@ + + +from fluidsim.solvers.sw1l.init_fields import InitFieldsSW1l + + +class InitFieldsSW1lModified(InitFieldsSW1l): + """Init """ + + + def fill_state_from_uxuyfft(self, ux_fft, uy_fft): + sim = self.sim + oper = sim.oper + ifft2 = oper.ifft2 + + oper.projection_perp(ux_fft, uy_fft) + oper.dealiasing(ux_fft, uy_fft) + + ux = ifft2(ux_fft) + uy = ifft2(uy_fft) + + rot_fft = oper.rotfft_from_vecfft(ux_fft, uy_fft) + rot = ifft2(rot_fft) + + eta_fft = self.etafft_no_div(ux, uy, rot) + eta = ifft2(eta_fft) + + state_fft = sim.state.state_fft + state_fft['ux_fft'] = ux_fft + state_fft['uy_fft'] = uy_fft + state_fft['eta_fft'] = eta_fft + + state_phys = sim.state.state_phys + state_phys['ux'] = ux + state_phys['uy'] = uy + state_phys['eta'] = eta + + + + diff --git a/fluidsim/solvers/sw1l/modified/output.py b/fluidsim/solvers/sw1l/modified/output.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL21vZGlmaWVkL291dHB1dC5weQ== --- /dev/null +++ b/fluidsim/solvers/sw1l/modified/output.py @@ -0,0 +1,53 @@ +""" """ + +import numpy as np + +from fluidsim.solvers.sw1l.output.output_base import OutputBaseSW1l + + +class OutputSW1lModified(OutputBaseSW1l): + """subclass :class:`OutputSW1l`""" + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + OutputBaseSW1l._complete_info_solver(info_solver) + + classes = info_solver.classes.Output.classes + + classes.SpatialMeans.class_name = 'SpatialMeansMSW1l' + classes.SpectralEnergyBudget.class_name = 'SpectralEnergyBudgetMSW1l' + + def compute_energies_fft(self): + ux_fft = self.sim.state.state_fft['ux_fft'] + uy_fft = self.sim.state.state_fft['uy_fft'] + eta_fft = self.sim.state.state_fft['eta_fft'] + energyA_fft = self.sim.params.c2 * np.abs(eta_fft)**2/2 + energyK_fft = np.abs(ux_fft)**2/2 + np.abs(uy_fft)**2/2 + rot_fft = self.rotfft_from_vecfft(ux_fft, uy_fft) + uxr_fft, uyr_fft = self.vecfft_from_rotfft(rot_fft) + energyKr_fft = np.abs(uxr_fft)**2/2 + np.abs(uyr_fft)**2/2 + return energyK_fft, energyA_fft, energyKr_fft + + def compute_energiesKA_fft(self): + ux_fft = self.sim.state.state_fft['ux_fft'] + uy_fft = self.sim.state.state_fft['uy_fft'] + eta_fft = self.sim.state.state_fft['eta_fft'] + energyA_fft = self.sim.params.c2 * np.abs(eta_fft)**2/2 + energyK_fft = np.abs(ux_fft)**2/2 + np.abs(uy_fft)**2/2 + return energyK_fft, energyA_fft + + def compute_PV_fft(self): + # compute Ertel and Charney (QG) potential vorticity + rot = self.sim.state('rot') + eta = self.sim.state.state_phys['eta'] + ErtelPV_fft = self.fft2((self.sim.params.f+rot)/(1.+eta)) + ux_fft = self.sim.state.state_fft['ux_fft'] + uy_fft = self.sim.state.state_fft['uy_fft'] + rot_fft = self.rotfft_from_vecfft(ux_fft, uy_fft) + eta_fft = self.sim.state.state_fft['eta_fft'] + CharneyPV_fft = rot_fft - self.sim.params.f*eta_fft + return ErtelPV_fft, CharneyPV_fft diff --git a/fluidsim/solvers/sw1l/modified/solver.py b/fluidsim/solvers/sw1l/modified/solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL21vZGlmaWVkL3NvbHZlci5weQ== --- /dev/null +++ b/fluidsim/solvers/sw1l/modified/solver.py @@ -0,0 +1,191 @@ +"""Modified SW1l equations +========================== + +(:mod:`fluidsim.solvers.sw1l.modified.solver`) + +This class is a solver of a modified version of the 1 layer shallow +water (Saint Venant) equations for which the advection is only +due to the rotational velocity. +""" + +from __future__ import division, print_function + +import numpy as np + +from fluidsim.operators.setofvariables import SetOfVariables + +from fluidsim.solvers.sw1l.solver import InfoSolverSW1l +from fluidsim.solvers.sw1l.solver import Simul as SimulSW1l + + +class InfoSolverSW1lModified(InfoSolverSW1l): + """Information about the solver SW1l.""" + def __init__(self, **kargs): + super(InfoSolverSW1lModified, self).__init__(**kargs) + + if 'tag' in kargs and kargs['tag'] == 'solver': + + sw1l = 'fluidsim.solvers.sw1l' + + self.module_name = sw1l+'.modified.solver' + self.class_name = 'Simul' + self.short_name = 'SW1lmodif' + + classes = self.classes + + classes.State.module_name = sw1l+'.modified.state' + classes.State.class_name = 'StateSW1lModified' + + classes.InitFields.module_name = sw1l+'.modified.init_fields' + classes.InitFields.class_name = 'InitFieldsSW1lModified' + + classes.Output.module_name = sw1l+'.modified.output' + classes.Output.class_name = 'OutputSW1lModified' + + +info_solver = InfoSolverSW1lModified(tag='solver') +info_solver.complete_with_classes() + + +class Simul(SimulSW1l): + """A solver of the shallow-water 1 layer equations (SW1l)""" + + def __init__(self, params, info_solver=info_solver): + super(Simul, self).__init__(params, info_solver) + + def tendencies_nonlin(self, state_fft=None): + oper = self.oper + fft2 = oper.fft2 + ifft2 = oper.ifft2 + + if state_fft is None: + state_phys = self.state.state_phys + state_fft = self.state.state_fft + else: + state_phys = self.state.return_statephys_from_statefft(state_fft) + + ux = state_phys['ux'] + uy = state_phys['uy'] + # eta = state_phys['eta'] + + ux_fft = state_fft['ux_fft'] + uy_fft = state_fft['uy_fft'] + eta_fft = state_fft['eta_fft'] + + # compute Fx_fft and Fy_fft + rot_fft = oper.rotfft_from_vecfft(ux_fft, uy_fft) + ux_rot_fft, uy_rot_fft = oper.vecfft_from_rotfft(rot_fft) + ux_rot = ifft2(ux_rot_fft) + uy_rot = ifft2(uy_rot_fft) + + dxux_fft, dyux_fft = oper.gradfft_from_fft(ux_fft) + dxux = ifft2(dxux_fft) + dyux = ifft2(dyux_fft) + dxuy_fft, dyuy_fft = oper.gradfft_from_fft(uy_fft) + dxuy = ifft2(dxuy_fft) + dyuy = ifft2(dyuy_fft) + + FNLx = -ux_rot*dxux - uy_rot*dyux + FNLy = -ux_rot*dxuy - uy_rot*dyuy + + FCx = +self.params.f*uy + FCy = -self.params.f*ux + + Fgradx_fft, Fgrady_fft = oper.gradfft_from_fft(self.params.c2*eta_fft) + + Fx_fft = fft2(FCx+FNLx) - Fgradx_fft + Fy_fft = fft2(FCy+FNLy) - Fgrady_fft + + # compute Feta_fft + dxeta_fft, dyeta_fft = oper.gradfft_from_fft(eta_fft) + dxeta = ifft2(dxeta_fft) + dyeta = ifft2(dyeta_fft) + + div_fft = oper.divfft_from_vecfft(ux_fft, uy_fft) + Feta_fft = -fft2(ux_rot*dxeta + uy_rot*dyeta) - div_fft + + oper.dealiasing(Fx_fft, Fy_fft, Feta_fft) + + # # for verification conservation energy + # T_ux = (ux_fft.conj()*Fx_fft).real + # T_uy = (uy_fft.conj()*Fy_fft).real + # T_eta = (eta_fft.conj()*Feta_fft).real + # T_tot = T_ux + T_uy + T_eta + # print 'sum(T_tot) = {0:9.4e} ; sum(abs(T_tot)) = {1:9.4e}'.format( + # self.oper.sum_wavenumbers(T_tot), + # self.oper.sum_wavenumbers(abs(T_tot))) + + tendencies_fft = SetOfVariables( + like_this_sov=self.state.state_fft, + name_type_variables='tendencies_nonlin') + + tendencies_fft['ux_fft'] = Fx_fft + tendencies_fft['uy_fft'] = Fy_fft + tendencies_fft['eta_fft'] = Feta_fft + + if self.params.FORCING: + tendencies_fft += self.forcing.get_forcing() + + return tendencies_fft + + + + + + + + + + + + +if __name__=="__main__": + + + import fluiddyn as fld + + params = fld.simul.create_params(info_solver) + + params.short_name_type_run = 'test' + + nh = 64 + Lh = 2*np.pi + params.oper.nx = nh + params.oper.ny = nh + params.oper.Lx = Lh + params.oper.Ly = Lh + + delta_x = params.oper.Lx/params.oper.nx + params.nu_8 = 2.*10e-1*params.forcing.forcing_rate**(1./3)*delta_x**8 + + params.time_stepping.t_end = 2. + + params.init_fields.type_flow_init = 'NOISE' + + + params.output.periods_print.print_stdout = 0.25 + + params.output.periods_save.phys_fields = 1. + params.output.periods_save.spectra = 0.5 + params.output.periods_save.spect_energy_budg = 0.5 + params.output.periods_save.increments = 0.5 + params.output.periods_save.pdf = 0.5 + params.output.periods_save.time_signals_fft = False + + params.output.periods_plot.phys_fields = 0. + + params.output.phys_fields.field_to_plot = 'div' + + params.output.spectra.has_to_plot = False + params.output.spatial_means.has_to_plot = False + params.output.spect_energy_budg.has_to_plot = False + params.output.increments.has_to_plot = False + params.output.pdf.has_to_plot = False + + sim = Simul(params) + + # sim.output.phys_fields.plot() + sim.time_stepping.start() + # sim.output.phys_fields.plot() + + fld.show() diff --git a/fluidsim/solvers/sw1l/modified/state.py b/fluidsim/solvers/sw1l/modified/state.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL21vZGlmaWVkL3N0YXRlLnB5 --- /dev/null +++ b/fluidsim/solvers/sw1l/modified/state.py @@ -0,0 +1,88 @@ +"""State class for the sw1l.modified solver +(:mod:`fluidsim.solvers.sw1l.modified.state`) +=================================================== + +.. currentmodule:: fluidsim.solvers.sw1l.modified.state + +Provides: + +.. autoclass:: StateSW1lModified + :members: + :private-members: + +""" + +from fluidsim.base.state import StatePseudoSpectral + +from fluiddyn.util import mpi + + +class StateSW1lModified(StatePseudoSpectral): + """ + The class :class:`StateMSW1l` contains the variables corresponding + to the state and handles the access to other fields for the solver + MSW1l. + """ + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + info_solver.classes.State.set_attribs({ + 'keys_state_fft': ['ux_fft', 'uy_fft', 'eta_fft'], + 'keys_state_phys': ['ux', 'uy', 'eta'], + 'keys_computable': [], + 'keys_phys_needed': ['ux', 'uy', 'eta'], + 'keys_linear_eigenmodes': ['q_fft', 'a_fft', 'd_fft']}) + + + def compute(self, key, SAVE_IN_DICT=True, RAISE_ERROR=True): + it = self.sim.time_stepping.it + + if (key in self.vars_computed and it == self.it_computed[key]): + return self.vars_computed[key] + + if key == 'ux_fft': + result = self.oper.fft2(self.state_phys['ux']) + elif key == 'uy_fft': + result = self.oper.fft2(self.state_phys['ux']) + elif key == 'rot_fft': + ux_fft = self.compute('ux_fft') + uy_fft = self.compute('uy_fft') + result = self.oper.rotfft_from_vecfft(ux_fft, uy_fft) + elif key == 'div_fft': + ux_fft = self.compute('ux_fft') + uy_fft = self.compute('uy_fft') + result = self.oper.divfft_from_vecfft(ux_fft, uy_fft) + elif key == 'rot': + rot_fft = self.compute('rot_fft') + result = self.oper.ifft2(rot_fft) + elif key == 'div': + div_fft = self.compute('div_fft') + result = self.oper.ifft2(div_fft) + elif key == 'q': + rot = self.compute('rot') + eta = self.sim.vars.state_phys['eta'] + result = rot-self.f*eta + else: + to_print = 'Do not know how to compute "'+key+'".' + if RAISE_ERROR: + raise ValueError(to_print) + else: + if mpi.rank == 0: + print(to_print + +'\nreturn an array of zeros.') + + result = self.oper.constant_arrayX(value=0.) + + if SAVE_IN_DICT: + self.vars_computed[key] = result + self.it_computed[key] = it + + return result + + + + + diff --git a/fluidsim/solvers/sw1l/onlywaves/__init__.py b/fluidsim/solvers/sw1l/onlywaves/__init__.py new file mode 100644 diff --git a/fluidsim/solvers/sw1l/onlywaves/solver.py b/fluidsim/solvers/sw1l/onlywaves/solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL29ubHl3YXZlcy9zb2x2ZXIucHk= --- /dev/null +++ b/fluidsim/solvers/sw1l/onlywaves/solver.py @@ -0,0 +1,225 @@ +"""SW1l equations solving exactly the linear terms +================================================== + +(:mod:`fluidsim.solvers.sw1l.onlywaves.solver`) + +This class is a solver of the 1 layer shallow water (Saint Venant) +equations with zeros QG PV. +""" + +from __future__ import division, print_function + +import numpy as np + +from fluidsim.operators.setofvariables import SetOfVariables + +from fluidsim.solvers.sw1l.exactlin.solver import InfoSolverSW1lExactLin +from fluidsim.solvers.sw1l.exactlin.solver import \ + Simul as SimulSW1lExactLin + + +from fluiddyn.util import mpi + + +class InfoSolverSW1lWaves(InfoSolverSW1lExactLin): + """Information about the solver SW1l.""" + def __init__(self, **kargs): + super(InfoSolverSW1lWaves, self).__init__(**kargs) + + if 'tag' in kargs and kargs['tag'] == 'solver': + + sw1l = 'fluidsim.solvers.sw1l' + + self.module_name = sw1l+'.onlywaves.solver' + self.short_name = 'SW1lwaves' + + classes = self.classes + + classes.State.module_name = sw1l+'.onlywaves.state' + classes.State.class_name = 'StateSW1lWaves' + + classes.InitFields.class_name = 'InitFieldsSW1lWaves' + + classes.Forcing.class_name = 'ForcingSW1lWaves' + + +info_solver = InfoSolverSW1lWaves(tag='solver') +info_solver.complete_with_classes() + + +class Simul(SimulSW1lExactLin): + """A solver of the shallow-water 1 layer equations (SW1l)""" + + def __init__(self, params, info_solver=info_solver): + super(Simul, self).__init__(params, info_solver) + + def tendencies_nonlin(self, state_fft=None): + oper = self.oper + fft2 = oper.fft2 + + if state_fft is None: + state_phys = self.state.state_phys + state_fft = self.state.state_fft + else: + state_phys = self.state.return_statephys_from_statefft(state_fft) + + ux = state_phys['ux'] + uy = state_phys['uy'] + eta = state_phys['eta'] + + # compute the nonlinear terms for ux, uy and eta + gradu2_x_fft, gradu2_y_fft = oper.gradfft_from_fft( + fft2(ux**2+uy**2)/2) + + Nx_fft = - gradu2_x_fft + Ny_fft = - gradu2_y_fft + + if self.params.f > 0: + # this is not very efficient, but this is simple... + rot = self.state('rot') + N1x = +rot*uy + N1y = -rot*ux + + Nx_fft += fft2(N1x) + Ny_fft += fft2(N1y) + + jx_fft = fft2(eta*ux) + jy_fft = fft2(eta*uy) + Neta_fft = -oper.divfft_from_vecfft(jx_fft, jy_fft) + + # self.verify_tendencies(state_fft, state_phys, + # Nx_fft, Ny_fft, Neta_fft) + + # compute the nonlinear terms for q, ap and am + (Nq_fft, Np_fft, Nm_fft + ) = self.oper.qapamfft_from_uxuyetafft(Nx_fft, Ny_fft, Neta_fft) + + # Np_fft = self.oper.constant_arrayK(value=0) + # Nm_fft = self.oper.constant_arrayK(value=0) + + oper.dealiasing(Np_fft, Nm_fft) + + tendencies_fft = SetOfVariables( + like_this_sov=self.state.state_fft, + name_type_variables='tendencies_nonlin') + tendencies_fft['ap_fft'] = Np_fft + tendencies_fft['am_fft'] = Nm_fft + + if self.params.FORCING: + tendencies_fft += self.forcing.get_forcing() + + return tendencies_fft + + def compute_freq_complex(self, key): + K2 = self.oper.K2 + # return self.oper.constant_arrayK(value=0) + if key == 'ap_fft': + omega = 1.j*np.sqrt(self.params.f**2 + self.params.c2*K2) + elif key == 'am_fft': + omega = -1.j*np.sqrt(self.params.f**2 + self.params.c2*K2) + return omega + + def verify_tendencies(self, state_fft, state_phys, + Nx_fft, Ny_fft, Neta_fft): + # for verification conservation energy + # compute the linear terms + oper = self.oper + ux = state_phys['ux'] + uy = state_phys['uy'] + eta = state_phys['eta'] + + # q_fft = self.oper.constant_arrayK(value=0) + ap_fft = state_fft['ap_fft'] + am_fft = state_fft['am_fft'] + a_fft = ap_fft + am_fft + div_fft = self.divfft_from_apamfft(ap_fft, am_fft) + + eta_fft = oper.etafft_from_afft(a_fft) + + dx_c2eta_fft, dy_c2eta_fft = oper.gradfft_from_fft( + self.params.c2*eta_fft) + LCx = self.params.f*uy + LCy = -self.params.f*ux + Lx_fft = oper.fft2(LCx) - dx_c2eta_fft + Ly_fft = oper.fft2(LCy) - dy_c2eta_fft + Leta_fft = -div_fft + + # compute the full tendencies + Fx_fft = Lx_fft + Nx_fft + Fy_fft = Ly_fft + Ny_fft + Feta_fft = Leta_fft + Neta_fft + oper.dealiasing(Fx_fft, Fy_fft, Feta_fft) + + # test : ux, uy, eta ---> q, ap, am + (Fq_fft, Fp_fft, Fm_fft + ) = self.oper.qapamfft_from_uxuyetafft(Fx_fft, Fy_fft, Feta_fft) + # test : q, ap, am ---> ux, uy, eta + (Fx2_fft, Fy2_fft, Feta2_fft + ) = self.oper.uxuyetafft_from_qapamfft(Fq_fft, Fp_fft, Fm_fft) + print(np.max(abs(Fx2_fft - Fx_fft))) + print(np.max(abs(Fy2_fft - Fy_fft))) + print(np.max(abs(Feta2_fft - Feta_fft))) + Fx_fft = Fx2_fft + Fy_fft = Fy2_fft + Feta_fft = Feta2_fft + + (Fq2_fft, Fp2_fft, Fm2_fft + ) = self.oper.qapamfft_from_uxuyetafft( + Fx2_fft, Fy2_fft, Feta2_fft) + print(np.max(abs(Fq2_fft - Fq_fft))) + print(np.max(abs(Fp2_fft - Fp_fft))) + print(np.max(abs(Fm2_fft - Fm_fft))) + + Fx = oper.ifft2(Fx_fft) + Fy = oper.ifft2(Fy_fft) + Feta = oper.ifft2(Feta_fft) + A = (Feta*(ux**2+uy**2)/2 + + (1+eta)*(ux*Fx+uy*Fy) + + self.params.c2*eta*Feta) + A_fft = oper.fft2(A) + if mpi.rank == 0: + print('should be zero =', A_fft[0, 0]) + + +if __name__ == "__main__": + + import fluiddyn as fld + + params = fld.simul.create_params(info_solver) + + params.short_name_type_run = 'test' + + nh = 64 + Lh = 2*np.pi + params.oper.nx = nh + params.oper.ny = nh + params.oper.Lx = Lh + params.oper.Ly = Lh + + delta_x = params.oper.Lx/params.oper.nx + params.nu_8 = 2.*10e-1*params.forcing.forcing_rate**(1./3)*delta_x**8 + + params.time_stepping.t_end = 2. + + params.init_fields.type_flow_init = 'NOISE' + + params.output.periods_print.print_stdout = 0.25 + + params.output.periods_save.phys_fields = 1. + params.output.periods_save.spectra = 0.5 + params.output.periods_save.spect_energy_budg = 0.5 + params.output.periods_save.increments = 0.5 + params.output.periods_save.pdf = 0.5 + params.output.periods_save.time_signals_fft = False + + params.output.periods_plot.phys_fields = 0. + + params.output.phys_fields.field_to_plot = 'div' + + sim = Simul(params) + + # sim.output.phys_fields.plot() + sim.time_stepping.start() + # sim.output.phys_fields.plot() + + fld.show() diff --git a/fluidsim/solvers/sw1l/onlywaves/state.py b/fluidsim/solvers/sw1l/onlywaves/state.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL29ubHl3YXZlcy9zdGF0ZS5weQ== --- /dev/null +++ b/fluidsim/solvers/sw1l/onlywaves/state.py @@ -0,0 +1,194 @@ +"""State class for the SW1l.onlywaves solver +(:mod:`fluidsim.solvers.sw1l.onlywaves.state`) +==================================================== + +.. currentmodule:: fluidsim.solvers.sw1l.onlywaves.state + +Provides: + +.. autoclass:: StateSW1lWaves + :members: + :private-members: + +""" + +from fluidsim.operators.setofvariables import SetOfVariables + +from fluidsim.solvers.sw1l.state import StateSW1l + +from fluiddyn.util import mpi + + + +class StateSW1lWaves(StateSW1l): + """ + The class :class:`StateSW1lwaves` contains the variables corresponding + to the state and handles the access to other fields for the solver + SW1l. + """ + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + info_solver.classes.State.set_attribs({ + 'keys_state_fft': ['ap_fft', 'am_fft'], + 'keys_state_phys': ['ux', 'uy', 'eta'], + 'keys_computable': [], + 'keys_phys_needed': ['ux', 'uy', 'eta'], + 'keys_linear_eigenmodes': ['q_fft', 'a_fft', 'd_fft']}) + + + + + def compute(self, key, SAVE_IN_DICT=True, RAISE_ERROR=True): + it = self.sim.time_stepping.it + if (key in self.vars_computed and it == self.it_computed[key]): + return self.vars_computed[key] + + if key == 'div_fft': + ap_fft = self.state_fft['ap_fft'] + am_fft = self.state_fft['am_fft'] + d_fft = self.oper.divfft_from_apamfft(ap_fft, am_fft) + result = d_fft + + elif key == 'a_fft': + ap_fft = self.state_fft['ap_fft'] + am_fft = self.state_fft['am_fft'] + result = ap_fft + am_fft + + elif key == 'rot_fft': + a_fft = self.compute('a_fft') + result = self.oper.rotfft_from_afft(a_fft) + + elif key == 'eta_fft': + a_fft = self.compute('a_fft') + result = self.oper.etafft_from_afft(a_fft) + + + elif key == 'ux_fft': + rot_fft = self.compute('rot_fft') + div_fft = self.compute('div_fft') + urx_fft, ury_fft = self.oper.vecfft_from_rotfft(rot_fft) + udx_fft, udy_fft = self.oper.vecfft_from_divfft(div_fft) + ux_fft = urx_fft + udx_fft + if mpi.rank == 0: + ap_fft = self.state_fft['ap_fft'] + ux_fft[0, 0] = ap_fft[0, 0] + result = ux_fft + if SAVE_IN_DICT: + key2 = 'uy_fft' + uy_fft = ury_fft + udy_fft + if mpi.rank == 0: + am_fft = self.state_fft['am_fft'] + uy_fft[0, 0] = am_fft[0, 0] + + self.vars_computed[key2] = uy_fft + self.it_computed[key2] = it + + elif key == 'uy_fft': + rot_fft = self.compute('rot_fft') + div_fft = self.compute('div_fft') + urx_fft, ury_fft = self.oper.vecfft_from_rotfft(rot_fft) + udx_fft, udy_fft = self.oper.vecfft_from_divfft(div_fft) + uy_fft = ury_fft + udy_fft + if mpi.rank == 0: + am_fft = self.state_fft['am_fft'] + uy_fft[0, 0] = am_fft[0, 0] + result = uy_fft + if SAVE_IN_DICT: + key2 = 'ux_fft' + ux_fft = urx_fft + udx_fft + if mpi.rank == 0: + ap_fft = self.state_fft['ap_fft'] + ux_fft[0, 0] = ap_fft[0, 0] + self.vars_computed[key2] = ux_fft + self.it_computed[key2] = it + + elif key == 'rot': + rot_fft = self.compute('rot_fft') + result = self.oper.ifft2(rot_fft) + + elif key == 'q': + result = self.oper.constant_arrayX(value=0) + + else: + result = super(StateSW1lWaves, self).compute( + key, SAVE_IN_DICT=SAVE_IN_DICT, + RAISE_ERROR=RAISE_ERROR) + SAVE_IN_DICT = False + + # to_print = 'Do not know how to compute "'+key+'".' + # if RAISE_ERROR: + # raise ValueError(to_print) + # else: + # if mpi.rank == 0: + # print(to_print + # +'\nreturn an array of zeros.') + # result = self.oper.constant_arrayX(value=0.) + + if SAVE_IN_DICT: + self.vars_computed[key] = result + self.it_computed[key] = it + + return result + + + + + + def statefft_from_statephys(self): + """Compute the state in Fourier space.""" + ux = self.state_phys['ux'] + uy = self.state_phys['uy'] + eta = self.state_phys['eta'] + + eta_fft = self.oper.fft2(eta) + ux_fft = self.oper.fft2(ux) + uy_fft = self.oper.fft2(uy) + + (q_fft, ap_fft, am_fft + ) = self.oper.qapamfft_from_uxuyetafft(ux_fft, uy_fft, eta_fft) + + self.state_fft['ap_fft'] = ap_fft + self.state_fft['am_fft'] = am_fft + + + + def statephys_from_statefft(self): + """Compute the state in physical space.""" + ifft2 = self.oper.ifft2 + q_fft = self.oper.constant_arrayK(value=0) + ap_fft = self.state_fft['ap_fft'] + am_fft = self.state_fft['am_fft'] + + (ux_fft, uy_fft, eta_fft + ) = self.oper.uxuyetafft_from_qapamfft(q_fft, ap_fft, am_fft) + + self.state_phys['ux'] = ifft2(ux_fft) + self.state_phys['uy'] = ifft2(uy_fft) + self.state_phys['eta'] = ifft2(eta_fft) + + + + def return_statephys_from_statefft(self, state_fft=None): + """Return the state in physical space.""" + ifft2 = self.oper.ifft2 + if state_fft is None: + state_fft = self.state_fft + + q_fft = self.oper.constant_arrayK(value=0) + ap_fft = state_fft['ap_fft'] + am_fft = state_fft['am_fft'] + + (ux_fft, uy_fft, eta_fft + ) = self.oper.uxuyetafft_from_qapamfft(q_fft, ap_fft, am_fft) + + state_phys = SetOfVariables(like_this_sov=self.state_phys) + state_phys['ux'] = ifft2(ux_fft) + state_phys['uy'] = ifft2(uy_fft) + state_phys['eta'] = ifft2(eta_fft) + return state_phys + diff --git a/fluidsim/solvers/sw1l/output/__init__.py b/fluidsim/solvers/sw1l/output/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL291dHB1dC9fX2luaXRfXy5weQ== --- /dev/null +++ b/fluidsim/solvers/sw1l/output/__init__.py @@ -0,0 +1,184 @@ +""" """ + +import numpy as np + +from fluidsim.base.output import OutputBasePseudoSpectral + + +class OutputBaseSW1l(OutputBasePseudoSpectral): + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + info_solver.classes.Output.set_child('classes') + classes = info_solver.classes.Output.classes + + package = 'fluidsim.solvers.sw1l.output' + + classes.set_child( + 'PrintStdOut', + attribs={'module_name': package + '.print_stdout', + 'class_name': 'PrintStdOutSW1l'}) + + classes.set_child( + 'PhysFields', + attribs={'module_name': 'fluidsim.base.output.phys_fields', + 'class_name': 'PhysFieldsBase'}) + + classes.set_child( + 'Spectra', + attribs={'module_name': package + '.spectra', + 'class_name': 'SpectraSW1l'}) + + classes.set_child( + 'SpatialMeans', + attribs={'module_name': package + '.spatial_means', + 'class_name': 'SpatialMeansSW1l'}) + + attribs = { + 'module_name': package + '.spect_energy_budget', + 'class_name': 'SpectralEnergyBudgetSW1l'} + classes.set_child('SpectralEnergyBudget', attribs=attribs) + + attribs = { + 'module_name': 'fluidsim.base.output.increments', + 'class_name': 'IncrementsSW1l'} + classes.set_child('Increments', attribs=attribs) + + attribs = { + 'module_name': 'fluidsim.base.output.prob_dens_func', + 'class_name': 'ProbaDensityFunc'} + classes.set_child('ProbaDensityFunc', attribs=attribs) + + attribs = { + 'module_name': 'fluidsim.base.output.time_signalsK', + 'class_name': 'TimeSignalsK'} + classes.set_child('TimeSignalsK', attribs=attribs) + + @staticmethod + def _complete_params_with_default(params, info_solver): + """This static method is used to complete the *params* container. + """ + OutputBasePseudoSpectral._complete_params_with_default( + params, info_solver) + + params.output.phys_fields.field_to_plot = 'rot' + + def linear_eigenmode_from_values_1k(self, ux_fft, uy_fft, eta_fft, + kx, ky): + div_fft = 1j*(kx*ux_fft + ky*uy_fft) + rot_fft = 1j*(kx*uy_fft - ky*ux_fft) + q_fft = rot_fft - self.sim.params.f*eta_fft + k2 = kx**2+ky**2 + ageo_fft = self.sim.params.f*rot_fft/self.sim.params.c2 + k2*eta_fft + return q_fft, div_fft, ageo_fft + + def omega_from_wavenumber(self, k): + return np.sqrt(self.sim.params.f**2 + self.sim.params.c2*k**2) + + def compute_enstrophy_fft(self): + rot_fft = self.sim.state('rot_fft') + return np.abs(rot_fft)**2/2 + + def compute_PV_fft(self): + """Compute Ertel and Charney (QG) potential vorticity.""" + rot = self.sim.state('rot') + eta = self.sim.state.state_phys['eta'] + ErtelPV_fft = self.fft2((self.sim.params.f+rot)/(1.+eta)) + rot_fft = self.sim.state('rot_fft') + eta_fft = self.sim.state('eta_fft') + CharneyPV_fft = rot_fft - self.sim.params.f*eta_fft + return ErtelPV_fft, CharneyPV_fft + + def compute_PE_fft(self): + ErtelPV_fft, CharneyPV_fft = self.compute_PV_fft() + return (abs(ErtelPV_fft)**2/2, + abs(CharneyPV_fft)**2/2) + + def compute_CharneyPE_fft(self): + # compute Charney (QG) potential vorticity + rot_fft = self.sim.state('rot_fft') + eta_fft = self.sim.state('eta_fft') + CharneyPV_fft = rot_fft - self.sim.params.f*eta_fft + return abs(CharneyPV_fft)**2/2 + + def compute_energies(self): + energyK_fft, energyA_fft, energyKr_fft = self.compute_energies_fft() + return (self.sum_wavenumbers(energyK_fft), + self.sum_wavenumbers(energyA_fft), + self.sum_wavenumbers(energyKr_fft)) + + def compute_energiesKA(self): + energyK_fft, energyA_fft = self.compute_energiesKA_fft() + return (self.sum_wavenumbers(energyK_fft), + self.sum_wavenumbers(energyA_fft)) + + def compute_energy(self): + energyK_fft, energyA_fft = self.compute_energiesKA_fft() + return (self.sum_wavenumbers(energyK_fft) + + self.sum_wavenumbers(energyA_fft)) + + def compute_enstrophy(self): + enstrophy_fft = self.compute_enstrophy_fft() + return self.sum_wavenumbers(enstrophy_fft) + + def compute_lin_energies_fft(self): + """Compute quadratic energies.""" + + ux_fft = self.sim.state('ux_fft') + uy_fft = self.sim.state('uy_fft') + eta_fft = self.sim.state('eta_fft') + + q_fft, div_fft, ageo_fft = \ + self.oper.qdafft_from_uxuyetafft(ux_fft, uy_fft, eta_fft) + + udx_fft, udy_fft = self.oper.vecfft_from_divfft(div_fft) + energy_dlin_fft = 0.5*(np.abs(udx_fft)**2 + np.abs(udy_fft)**2) + + ugx_fft, ugy_fft, etag_fft = self.oper.uxuyetafft_from_qfft(q_fft) + energy_glin_fft = 0.5*(np.abs(ugx_fft)**2 + np.abs(ugy_fft)**2 + + self.sim.params.c2*np.abs(etag_fft)**2) + + uax_fft, uay_fft, etaa_fft = self.oper.uxuyetafft_from_afft(ageo_fft) + energy_alin_fft = 0.5*(np.abs(uax_fft)**2 + np.abs(uay_fft)**2 + + self.sim.params.c2*np.abs(etaa_fft)**2) + + return energy_glin_fft, energy_dlin_fft, energy_alin_fft + + +class OutputSW1l(OutputBaseSW1l): + + def compute_energies_fft(self): + state = self.sim.state + eta_fft = state('eta_fft') + energyA_fft = self.sim.params.c2 * np.abs(eta_fft)**2/2 + Jx_fft = state('Jx_fft') + Jy_fft = state('Jy_fft') + ux_fft = state('ux_fft') + uy_fft = state('uy_fft') + energyK_fft = np.real(Jx_fft.conj()*ux_fft + + Jy_fft.conj()*uy_fft)/2 + + rot_fft = state('rot_fft') + uxr_fft, uyr_fft = self.vecfft_from_rotfft(rot_fft) + rotJ_fft = self.rotfft_from_vecfft(Jx_fft, Jy_fft) + Jxr_fft, Jyr_fft = self.vecfft_from_rotfft(rotJ_fft) + energyKr_fft = np.real(Jxr_fft.conj()*uxr_fft + + Jyr_fft.conj()*uyr_fft)/2 + return energyK_fft, energyA_fft, energyKr_fft + + def compute_energiesKA_fft(self): + state = self.sim.state + eta_fft = state('eta_fft') + energyA_fft = self.sim.params.c2 * np.abs(eta_fft)**2/2 + Jx_fft = state('Jx_fft') + Jy_fft = state('Jy_fft') + ux_fft = state('ux_fft') + uy_fft = state('uy_fft') + energyK_fft = np.real(Jx_fft.conj()*ux_fft + + Jy_fft.conj()*uy_fft)/2 + + return energyK_fft, energyA_fft diff --git a/fluidsim/solvers/sw1l/output/print_stdout.py b/fluidsim/solvers/sw1l/output/print_stdout.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL291dHB1dC9wcmludF9zdGRvdXQucHk= --- /dev/null +++ b/fluidsim/solvers/sw1l/output/print_stdout.py @@ -0,0 +1,157 @@ + +from __future__ import print_function, division + +from time import time +import numpy as np + +from fluidsim.base.output.print_stdout import PrintStdOutBase + +from fluiddyn.util import mpi + + +class PrintStdOutSW1l(PrintStdOutBase): + """A :class:`PrintStdOutBase` object is used to print in both the + stdout and the stdout.txt file, and also to print simple info on + the current state of the simulation.""" + + def online_print(self): + tsim = self.sim.time_stepping.t + if (tsim-self.t_last_print_info <= self.period_print): + return + + itsim = self.sim.time_stepping.it + deltatsim = self.sim.time_stepping.deltat + + energyK, energyA = self.output.compute_energiesKA() + energy = energyK + energyA + if mpi.rank==0: + t_real_word = time() + if self.t_real_word_last == 0.: + duration_left = 0 + else: + if self.params.time_stepping.USE_T_END: + duration_left = int(np.round( + (self.params.time_stepping.t_end - tsim) + *(t_real_word-self.t_real_word_last) + /(tsim - self.t_last_print_info) + )) + else: + duration_left = int(np.round( + (self.params.time_stepping.it_end - itsim) + *(t_real_word-self.t_real_word_last) + )) + to_print = ( + 'it = {0:6d} ; t = {1:9.3f} ; deltat = {2:10.3g}\n' + ' energy = {3:8.3e} ; Delta energy = {4:8.3e}\n' + ' energyK = {5:8.3e} ; energyA = {6:8.3e}\n' + ' estimated remaining duration = {7:6d} s') + to_print = to_print.format( + itsim, tsim, deltatsim, + energy, energy-self.energy_temp, + energyK, energyA, + duration_left) + self.print_stdout(to_print) + self.t_real_word_last = t_real_word + self.energy_temp = energy + self.t_last_print_info = tsim + + def load(self): + dico_results = {'name_solver': self.output.name_solver} + file_means = open(self.output.path_run+'/stdout.txt') + lines = file_means.readlines() + + lines_t = [] + lines_E = [] + lines_E_KA = [] + for il, line in enumerate(lines): + if line[0:4]=='it =': + lines_t.append(line) + if line[0:23]==' energy =': + lines_E.append(line) + if line[0:23]==' energyK =': + lines_E_KA.append(line) + nt = len(lines_t) + if nt > 1: + nt -= 1 + + it = np.zeros(nt, dtype=np.int) + t = np.zeros(nt) + deltat = np.zeros(nt) + + E = np.zeros(nt) + deltaE = np.zeros(nt) + + E_K = np.zeros(nt) + E_A = np.zeros(nt) + + for il in xrange(nt): + line = lines_t[il] + words = line.split() + it[il] = int(words[2]) + t[il] = float(words[6]) + deltat[il] = float(words[10]) + + line = lines_E[il] + words = line.split() + E[il] = float(words[2]) + deltaE[il] = float(words[7]) + + line = lines_E_KA[il] + words = line.split() + E_K[il] = float(words[2]) + E_A[il] = float(words[6]) + + dico_results['it'] = it + dico_results['t'] = t + dico_results['deltat'] = deltat + dico_results['E'] = E + dico_results['deltaE'] = deltaE + dico_results['E_K'] = E_K + dico_results['E_A'] = E_A + return dico_results + + + def plot(self): + dico_results = self.load() + + it = dico_results['it'] + t = dico_results['t'] + deltat = dico_results['deltat'] + E = dico_results['E'] + deltaE = dico_results['deltaE'] + E_K = dico_results['E_K'] + E_A = dico_results['E_A'] + + x_left_axe = 0.12 + z_bottom_axe = 0.55 + width_axe = 0.85 + height_axe = 0.4 + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('t') + ax1.set_ylabel('deltat(t)') + title = ('info stdout, solver '+self.output.name_solver+ + ', nh = {0:5d}'.format(self.nx)) + + try: + title = title+', c = {0:.4g}, f = {1:.4g}'.format( + np.sqrt(self.c2), self.f) + except AttributeError: + pass + + ax1.set_title(title) + ax1.hold(True) + ax1.plot(t, deltat, 'k', linewidth=2 ) + + z_bottom_axe = 0.08 + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + ax2 = fig.add_axes(size_axe) + ax2.set_xlabel('t') + ax2.set_ylabel('E(t), deltaE(t)') + ax2.hold(True) + ax2.plot(t, E, 'k', linewidth=2 ) + ax2.plot(t, E_K, 'r', linewidth=2 ) + ax2.plot(t, E_A, 'b', linewidth=2 ) + ax2.plot(t, deltaE, 'k--', linewidth=2 ) diff --git a/fluidsim/solvers/sw1l/output/spatial_means.py b/fluidsim/solvers/sw1l/output/spatial_means.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL291dHB1dC9zcGF0aWFsX21lYW5zLnB5 --- /dev/null +++ b/fluidsim/solvers/sw1l/output/spatial_means.py @@ -0,0 +1,706 @@ + +import os +import numpy as np + +from fluiddyn.util import mpi +from fluidsim.base.output.spatial_means import ( + SpatialMeansBase, inner_prod) + + +class SpatialMeansMSW1l(SpatialMeansBase): + """A :class:`SpatialMean` object handles the saving of .""" + + def __init__(self, output): + + params = output.sim.params + self.c2 = params.c2 + self.f = params.f + + super(SpatialMeansMSW1l, self).__init__(output) + + def save_one_time(self): + tsim = self.sim.time_stepping.t + self.t_last_save = tsim + + if mpi.rank == 0: + self.file.write('####\ntime = {0:.6e}\n'.format(tsim)) + + energyK_fft, energyA_fft, energyKr_fft = \ + self.output.compute_energies_fft() + energyK = self.sum_wavenumbers(energyK_fft) + energyA = self.sum_wavenumbers(energyA_fft) + energyKr = self.sum_wavenumbers(energyKr_fft) + energy = energyK + energyA + + CharneyPE_fft = self.output.compute_CharneyPE_fft() + CharneyPE = self.sum_wavenumbers(CharneyPE_fft) + + if mpi.rank == 0: + to_print = ( + 'E = {0:11.6e} ; CPE = {1:11.6e} \n' + 'EK = {2:11.6e} ; EA = {3:11.6e} ; ' + 'EKr = {4:11.6e} \n').format( + energy, CharneyPE, energyK, energyA, energyKr) + self.file.write(to_print) + + # Compute and save dissipation rates. + self.treat_dissipation_rates(energyK_fft, energyA_fft, CharneyPE_fft) + + # Compute and save conversion rates. + self.treat_conversion() + + # Compute and save skewness and kurtosis. + eta = self.sim.state.state_phys['eta'] + meaneta2 = 2./self.c2*energyA + skew_eta = np.mean(eta**3)/meaneta2**(3./2) + kurt_eta = np.mean(eta**4)/meaneta2**(2) + + ux = self.sim.state.state_phys['ux'] + uy = self.sim.state.state_phys['uy'] + ux_fft = self.sim.oper.fft2(ux) + uy_fft = self.sim.oper.fft2(uy) + rot_fft = self.sim.oper.rotfft_from_vecfft(ux_fft, uy_fft) + rot = self.sim.oper.ifft2(rot_fft) + meanrot2 = self.sum_wavenumbers(abs(rot_fft)**2) + skew_rot = np.mean(rot**3)/meanrot2**(3./2) + kurt_rot = np.mean(rot**4)/meanrot2**(2) + + if mpi.rank == 0: + to_print = ( + 'eta skew = {0:11.6e} ; kurt = {1:11.6e} \n' + 'rot skew = {2:11.6e} ; kurt = {3:11.6e} \n').format( + skew_eta, kurt_eta, skew_rot, kurt_rot) + self.file.write(to_print) + + if self.sim.params.FORCING: + self.treat_forcing() + + if mpi.rank == 0: + self.file.flush() + os.fsync(self.file.fileno()) + + if self.has_to_plot and mpi.rank == 0: + self.axe_a.plot(tsim, energy, 'k.') + self.axe_a.plot(tsim, energyK, 'r.') + self.axe_a.plot(tsim, energyA, 'b.') + + if tsim-self.t_last_show >= self.period_show: + self.t_last_show = tsim + fig = self.axe_a.get_figure() + fig.canvas.draw() + + def treat_conversion(self): + mean_space = self.sim.oper.mean_space + + c2 = self.sim.params.c2 + eta = self.sim.state('eta') + div = self.sim.state('div') + h = eta + 1 + + Conv = c2/2*mean_space(h**2*div) + c2eta1d = c2*mean_space(eta*div) + c2eta2d = c2*mean_space(eta**2*div) + c2eta3d = c2*mean_space(eta**3*div) + + if mpi.rank == 0: + to_print = ( + 'Conv = {0:11.6e} ; c2eta1d = {1:11.6e} ; ' + 'c2eta2d = {2:11.6e} ; c2eta2d = {3:11.6e}\n').format( + Conv, c2eta1d, c2eta2d, c2eta3d) + self.file.write(to_print) + + def treat_dissipation_rates(self, energyK_fft, energyA_fft, + CharneyPE_fft): + """Compute and save dissipation rates.""" + + f_d, f_d_hypo = self.sim.time_stepping.compute_freq_diss() + + dico_eps = self.compute_dissipation_rates( + f_d, f_d_hypo, + energyK_fft, energyA_fft, CharneyPE_fft) + + self.save_dissipation_rates(dico_eps) + + def compute_dissipation_rates( + self, f_d, f_d_hypo, + energyK_fft, energyA_fft, CharneyPE_fft): + + epsK = self.sum_wavenumbers(f_d*2*energyK_fft) + epsK_hypo = self.sum_wavenumbers(f_d_hypo*2*energyK_fft) + epsA = self.sum_wavenumbers(f_d*2*energyA_fft) + epsA_hypo = self.sum_wavenumbers(f_d_hypo*2*energyA_fft) + epsCPE = self.sum_wavenumbers(f_d*2*CharneyPE_fft) + epsCPE_hypo = self.sum_wavenumbers(f_d_hypo*2*CharneyPE_fft) + + dico_eps = {'epsK': epsK, + 'epsK_hypo': epsK_hypo, + 'epsA': epsA, + 'epsA_hypo': epsA_hypo, + 'epsCPE': epsCPE, + 'epsCPE_hypo': epsCPE_hypo} + return dico_eps + + def save_dissipation_rates(self, dico_eps): + epsK = dico_eps['epsK'] + epsK_hypo = dico_eps['epsK_hypo'] + epsA = dico_eps['epsA'] + epsA_hypo = dico_eps['epsA_hypo'] + epsCPE = dico_eps['epsCPE'] + epsCPE_hypo = dico_eps['epsCPE_hypo'] + + if mpi.rank == 0: + epsK_tot = epsK+epsK_hypo + epsA_tot = epsA+epsA_hypo + + to_print = ( +'epsK = {0:11.6e} ; epsK_hypo = {1:11.6e} ; epsK_tot = {2:11.6e} \n' +'epsA = {3:11.6e} ; epsA_hypo = {4:11.6e} ; epsA_tot = {5:11.6e} \n' +'epsCPE = {6:11.6e} ; epsCPEhypo = {7:11.6e} ; epsCPEtot = {8:11.6e} \n' +).format(epsK, epsK_hypo, epsK_tot, + epsA, epsA_hypo, epsA_tot, + epsCPE, epsCPE_hypo, epsCPE+epsCPE_hypo) + self.file.write(to_print) + + if self.has_to_plot: + tsim = self.sim.time_stepping.t + self.axe_b.plot(tsim, epsK_tot+epsA_tot, 'k.') + + def treat_forcing(self): + """Save forcing injection rates.""" + state_fft = self.sim.state.state_fft + ux_fft = state_fft['ux_fft'] + uy_fft = state_fft['uy_fft'] + eta_fft = state_fft['eta_fft'] + + forcing_fft = self.sim.forcing.get_forcing() + Fx_fft = forcing_fft['ux_fft'] + Fy_fft = forcing_fft['uy_fft'] + Feta_fft = forcing_fft['eta_fft'] + + deltat = self.sim.time_stepping.deltat + + PK1_fft = ( + inner_prod(ux_fft, Fx_fft) + + inner_prod(uy_fft, Fy_fft) + ) + PK2_fft = deltat/2*( abs(Fx_fft)**2 + abs(Fy_fft)**2 ) + + PK1 = self.sum_wavenumbers(PK1_fft) + PK2 = self.sum_wavenumbers(PK2_fft) + + PA1_fft = self.c2*inner_prod(eta_fft, Feta_fft) + PA2_fft = deltat/2*self.c2*(abs(Feta_fft)**2) + + PA1 = self.sum_wavenumbers(PA1_fft) + PA2 = self.sum_wavenumbers(PA2_fft) + + + if mpi.rank==0: + + PK_tot = PK1+PK2 + PA_tot = PA1+PA2 + to_print = ( +'PK1 = {0:11.6e} ; PK2 = {1:11.6e} ; PK_tot = {2:11.6e} \n' +'PA1 = {3:11.6e} ; PA2 = {4:11.6e} ; PA_tot = {5:11.6e} \n' +).format(PK1, PK2, PK_tot, PA1, PA2, PA_tot) + + self.file.write(to_print) + + if self.has_to_plot and mpi.rank == 0: + tsim = self.sim.time_stepping.t + self.axe_b.plot(tsim, PK_tot+PA_tot, 'c.') + + + + def load(self): + dico_results = {'name_solver': self.output.name_solver} + + file_means = open(self.path_file) + lines = file_means.readlines() + + lines_t = [] + lines_E = [] + lines_EK = [] + lines_epsK = [] + lines_epsA = [] + lines_epsCPE = [] + + lines_epsK = [] + + + lines_PK = [] + lines_PA = [] + lines_etaskew = [] + lines_rotskew = [] + lines_Conv = [] + + for il, line in enumerate(lines): + if line[0:6]=='time =': + lines_t.append(line) + if line[0:8]=='E =': + lines_E.append(line) + if line[0:8]=='EK =': + lines_EK.append(line) + if line[0:8]=='epsK =': + lines_epsK.append(line) + if line[0:8]=='epsA =': + lines_epsA.append(line) + if line[0:8]=='epsCPE =': + lines_epsCPE.append(line) + if line[0:8]=='PK1 =': + lines_PK.append(line) + if line[0:8]=='PA1 =': + lines_PA.append(line) + if line.startswith('eta skew ='): + lines_etaskew.append(line) + if line.startswith('rot skew ='): + lines_rotskew.append(line) + if line.startswith('Conv ='): + lines_Conv.append(line) + + nt = len(lines_t) + if nt>1: + nt -= 1 + + t = np.empty(nt) + + E = np.empty(nt) + CPE = np.empty(nt) + EK = np.empty(nt) + EA = np.empty(nt) + EKr = np.empty(nt) + epsK = np.empty(nt) + epsK_hypo = np.empty(nt) + epsK_tot = np.empty(nt) + epsA = np.empty(nt) + epsA_hypo = np.empty(nt) + epsA_tot = np.empty(nt) + epsCPE = np.empty(nt) + epsCPE_hypo = np.empty(nt) + epsCPE_tot = np.empty(nt) + + + if len(lines_PK) == len(lines_t): + PK1 = np.empty(nt) + PK2 = np.empty(nt) + PK_tot = np.empty(nt) + PA1 = np.empty(nt) + PA2 = np.empty(nt) + PA_tot = np.empty(nt) + + if len(lines_rotskew) == len(lines_t): + skew_eta = np.empty(nt) + kurt_eta = np.empty(nt) + skew_rot = np.empty(nt) + kurt_rot = np.empty(nt) + + if len(lines_Conv) == len(lines_t): + Conv = np.empty(nt) + c2eta1d = np.empty(nt) + c2eta2d = np.empty(nt) + c2eta3d = np.empty(nt) + + for il in xrange(nt): + line = lines_t[il] + words = line.split() + t[il] = float(words[2]) + + line = lines_E[il] + words = line.split() + E[il] = float(words[2]) + CPE[il] = float(words[6]) + + line = lines_EK[il] + words = line.split() + EK[il] = float(words[2]) + EA[il] = float(words[6]) + EKr[il] = float(words[10]) + + line = lines_epsK[il] + words = line.split() + epsK[il] = float(words[2]) + epsK_hypo[il] = float(words[6]) + epsK_tot[il] = float(words[10]) + + line = lines_epsA[il] + words = line.split() + epsA[il] = float(words[2]) + epsA_hypo[il] = float(words[6]) + epsA_tot[il] = float(words[10]) + + line = lines_epsCPE[il] + words = line.split() + epsCPE[il] = float(words[2]) + epsCPE_hypo[il] = float(words[6]) + epsCPE_tot[il] = float(words[10]) + + + if len(lines_PK) == len(lines_t): + line = lines_PK[il] + words = line.split() + PK1[il] = float(words[2]) + PK2[il] = float(words[6]) + PK_tot[il] = float(words[10]) + + line = lines_PA[il] + words = line.split() + PA1[il] = float(words[2]) + PA2[il] = float(words[6]) + PA_tot[il] = float(words[10]) + + if len(lines_rotskew) == len(lines_t): + line = lines_etaskew[il] + words = line.split() + skew_eta[il] = float(words[3]) + kurt_eta[il] = float(words[7]) + + line = lines_rotskew[il] + words = line.split() + skew_rot[il] = float(words[3]) + kurt_rot[il] = float(words[7]) + + if len(lines_Conv) == len(lines_t): + line = lines_Conv[il] + words = line.split() + Conv[il] = float(words[2]) + c2eta1d[il] = float(words[6]) + c2eta2d[il] = float(words[10]) + c2eta3d[il] = float(words[14]) + + + + + + + dico_results['t'] = t + dico_results['E'] = E + dico_results['CPE'] = CPE + + dico_results['EK'] = EK + dico_results['EA'] = EA + dico_results['EKr'] = EKr + + dico_results['epsK'] = epsK + dico_results['epsK_hypo'] = epsK_hypo + dico_results['epsK_tot'] = epsK_tot + + dico_results['epsA'] = epsA + dico_results['epsA_hypo'] = epsA_hypo + dico_results['epsA_tot'] = epsA_tot + + dico_results['epsCPE'] = epsCPE + dico_results['epsCPE_hypo'] = epsCPE_hypo + dico_results['epsCPE_tot'] = epsCPE_tot + + + if len(lines_PK) == len(lines_t): + dico_results['PK1'] = PK1 + dico_results['PK2'] = PK2 + dico_results['PK_tot'] = PK_tot + dico_results['PA1'] = PA1 + dico_results['PA2'] = PA2 + dico_results['PA_tot'] = PA_tot + + if len(lines_rotskew) == len(lines_t): + dico_results['skew_eta'] = skew_eta + dico_results['kurt_eta'] = kurt_eta + dico_results['skew_rot'] = skew_rot + dico_results['kurt_rot'] = kurt_rot + + if len(lines_Conv) == len(lines_t): + dico_results['Conv'] = Conv + dico_results['c2eta1d'] = c2eta1d + dico_results['c2eta2d'] = c2eta2d + dico_results['c2eta3d'] = c2eta3d + + return dico_results + + + + def plot(self): + dico_results = self.load() + + t = dico_results['t'] + + E = dico_results['E'] + CPE = dico_results['CPE'] + + EK = dico_results['EK'] + EA = dico_results['EA'] + EKr = dico_results['EKr'] + + epsK = dico_results['epsK'] + epsK_hypo = dico_results['epsK_hypo'] + epsK_tot = dico_results['epsK_tot'] + + epsA = dico_results['epsA'] + epsA_hypo = dico_results['epsA_hypo'] + epsA_tot = dico_results['epsA_tot'] + + epsE = epsK + epsA + epsE_hypo = epsK_hypo + epsA_hypo + epsE_tot = epsK_tot + epsA_tot + + epsCPE = dico_results['epsCPE'] + epsCPE_hypo = dico_results['epsCPE_hypo'] + epsCPE_tot = dico_results['epsCPE_tot'] + + if 'PK_tot' in dico_results: + PK_tot = dico_results['PK_tot'] + PA_tot = dico_results['PA_tot'] + P_tot = PK_tot + PA_tot + + width_axe = 0.85 + height_axe = 0.37 + x_left_axe = 0.12 + z_bottom_axe = 0.56 + + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('t') + ax1.set_ylabel('$2E(t)/c^2$') + title = ('mean energy, solver '+self.output.name_solver+ + ', nh = {0:5d}'.format(self.nx)+ + ', c = {0:.4g}, f = {1:.4g}'.format(np.sqrt(self.c2), self.f)) + ax1.set_title(title) + ax1.hold(True) + norm = self.c2/2 + ax1.plot(t, E/norm, 'k', linewidth=2) + ax1.plot(t, EK/norm, 'r', linewidth=1) + ax1.plot(t, EA/norm, 'b', linewidth=1) + ax1.plot(t, EKr/norm, 'r--', linewidth=1) + ax1.plot(t, (EK-EKr)/norm, 'r:', linewidth=1) + + z_bottom_axe = 0.07 + size_axe[1] = z_bottom_axe + ax2 = fig.add_axes(size_axe) + ax2.set_xlabel('t') + ax2.set_ylabel('Charney PE(t)') + title = ('mean Charney PE(t)') + ax2.set_title(title) + ax2.hold(True) + ax2.plot(t, CPE, 'k', linewidth=2 ) + + + z_bottom_axe = 0.56 + size_axe[1] = z_bottom_axe + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('t') + ax1.set_ylabel('$P_E(t)$, $\epsilon(t)$') + title = ('forcing and dissipation, solver '+self.output.name_solver+ +', nh = {0:5d}'.format(self.nx)+ +', c = {0:.4g}, f = {1:.4g}'.format(np.sqrt(self.c2), self.f) +) + ax1.set_title(title) + ax1.hold(True) + if 'PK_tot' in dico_results: + (l_P_tot,) = ax1.plot(t, P_tot, 'c', linewidth=2 ) + l_P_tot.set_label('$P_{tot}$') + + (l_epsE,) = ax1.plot(t, epsE, 'k--', linewidth=2 ) + (l_epsE_hypo,) = ax1.plot(t, epsE_hypo, 'g', linewidth=2 ) + (l_epsE_tot,) = ax1.plot(t, epsE_tot, 'k', linewidth=2 ) + + l_epsE.set_label('$\epsilon$') + l_epsE_hypo.set_label('$\epsilon_{hypo}$') + l_epsE_tot.set_label('$\epsilon_{tot}$') + + ax1.legend(loc=2) + + + z_bottom_axe = 0.07 + size_axe[1] = z_bottom_axe + ax2 = fig.add_axes(size_axe) + ax2.set_xlabel('t') + ax2.set_ylabel('$\epsilon$ Charney PE(t)') + title = ('dissipation Charney PE') + ax2.set_title(title) + ax2.hold(True) + ax2.plot(t, epsCPE, 'k--', linewidth=2 ) + ax2.plot(t, epsCPE_hypo, 'g', linewidth=2 ) + ax2.plot(t, epsCPE_tot, 'r', linewidth=2 ) + + + + + + +# skew_eta = dico_results['skew_eta'] +# kurt_eta = dico_results['kurt_eta'] +# skew_rot = dico_results['skew_rot'] +# kurt_rot = dico_results['kurt_rot'] + +# fig, ax1 = self.output.figure_axe() + +# title = ('skewness and kurtosis, solver '+self.output.name_solver+ +# ', nh = {0:5d}'.format(self.nx)+ +# ', c2 = {0:.4g}, f = {1:.4g}'.format(self.c2, self.f) +# ) +# ax1.set_title(title) +# ax2.set_xlabel('t') + +# ax1.plot(t, skew_eta, 'b', linewidth=2) +# ax1.plot(t, kurt_eta, 'b--', linewidth=2) +# ax1.plot(t, skew_rot, 'r', linewidth=2) +# ax1.plot(t, kurt_rot, 'r--', linewidth=2) + + + + + + + + + + +class SpatialMeansSW1l(SpatialMeansMSW1l): + """A :class:`SpatialMean` object handles the saving of .""" + + def treat_dissipation_rates(self, energyK_fft, energyA_fft, + CharneyPE_fft): + """Compute and save dissipation rates.""" + + f_d, f_d_hypo = self.sim.time_stepping.compute_freq_diss() + + dico_eps = super( + SpatialMeansSW1l, self + ).compute_dissipation_rates( + f_d, f_d_hypo, energyK_fft, energyA_fft, CharneyPE_fft) + + + (epsKsuppl, epsKsuppl_hypo + ) = self.compute_epsK(f_d, f_d_hypo, energyK_fft, dico_eps) + + super(SpatialMeansSW1l, self).save_dissipation_rates(dico_eps) + + if mpi.rank == 0: + to_print = ( +'epsKsup= {0:11.6e} ; epsKshypo = {1:11.6e} ;\n' +).format(epsKsuppl, epsKsuppl_hypo) + self.file.write(to_print) + + def compute_epsK(self, f_d, f_d_hypo, + energyK_fft, dico_eps): + + ux = self.sim.state.state_phys['ux'] + uy = self.sim.state.state_phys['uy'] + + EKquad = 0.5*(ux**2 + uy**2) + EKquad_fft = self.sim.oper.fft2(EKquad) + + eta_fft = self.sim.state('eta_fft') + + epsKsuppl = self.sum_wavenumbers( + f_d*inner_prod(EKquad_fft, eta_fft) + ) + + epsKsuppl_hypo = self.sum_wavenumbers( + f_d_hypo*inner_prod(EKquad_fft, eta_fft) + ) + + dico_eps['epsK'] += epsKsuppl + dico_eps['epsK_hypo'] += epsKsuppl_hypo + + return epsKsuppl, epsKsuppl_hypo + + + + def load(self): + + dico_results = super(SpatialMeansSW1l, self).load() + + file_means = open(self.path_file) + lines = file_means.readlines() + + lines_epsKsuppl = [] + + for il, line in enumerate(lines): + if line.startswith('epsKsup='): + lines_epsKsuppl.append(line) + + t = dico_results['t'] + nt = len(t) + epsKsuppl = np.empty(nt) + epsKsuppl_hypo = np.empty(nt) + + for il in xrange(nt): + line = lines_epsKsuppl[il] + words = line.split() + epsKsuppl[il] = float(words[1]) + epsKsuppl_hypo[il] = float(words[5]) + + dico_results['epsKsuppl'] = epsKsuppl + dico_results['epsKsuppl_hypo'] = epsKsuppl_hypo + + return dico_results + + + def treat_forcing(self): + """Save forcing injection rates.""" + state = self.sim.state + ux_fft = state('ux_fft') + uy_fft = state('uy_fft') + eta_fft = state('eta_fft') + + Fx_fft, Fy_fft, Feta_fft = \ + self.sim.forcing.get_FxFyFetafft() + + deltat = self.sim.time_stepping.deltat + + PA1_fft = self.c2*inner_prod(eta_fft, Feta_fft) + PA2_fft = deltat/2*self.c2*(abs(Feta_fft)**2) + + PA1 = self.sum_wavenumbers(PA1_fft) + PA2 = self.sum_wavenumbers(PA2_fft) + + Fx = self.sim.oper.ifft2(Fx_fft) + Fy = self.sim.oper.ifft2(Fy_fft) + Feta = self.sim.oper.ifft2(Feta_fft) + + eta = self.sim.state.state_phys['eta'] + h = eta + 1. + + ux = self.sim.state.state_phys['ux'] + uy = self.sim.state.state_phys['uy'] + + FetaFx_fft = self.sim.oper.fft2(Feta*Fx) + FetaFy_fft = self.sim.oper.fft2(Feta*Fy) + + Jx_fft = self.sim.oper.fft2(h*ux) + Jy_fft = self.sim.oper.fft2(h*uy) + + FJx_fft = self.sim.oper.fft2(h*Fx + Feta*ux) + FJy_fft = self.sim.oper.fft2(h*Fy + Feta*uy) + + PK1_fft = 0.5*( + inner_prod(Jx_fft, Fx_fft) + + inner_prod(Jy_fft, Fy_fft) + + inner_prod(ux_fft, FJx_fft) + + inner_prod(uy_fft, FJy_fft) + ) + PK2_fft = deltat/2*( + 0.5*(inner_prod(Fx_fft, FJx_fft) + + inner_prod(Fy_fft, FJy_fft) + ) + + inner_prod(ux_fft, FetaFx_fft) + + inner_prod(uy_fft, FetaFy_fft) + ) + + PK1 = self.sum_wavenumbers(PK1_fft) + PK2 = self.sum_wavenumbers(PK2_fft) + + if mpi.rank==0: + + PK_tot = PK1+PK2 + PA_tot = PA1+PA2 + to_print = ( +'PK1 = {0:11.6e} ; PK2 = {1:11.6e} ; PK_tot = {2:11.6e} \n' +'PA1 = {3:11.6e} ; PA2 = {4:11.6e} ; PA_tot = {5:11.6e} \n' +).format(PK1, PK2, PK_tot, PA1, PA2, PA_tot) + + self.file.write(to_print) + + if self.has_to_plot and mpi.rank == 0: + tsim = self.sim.time_stepping.t + self.axe_b.plot(tsim, PK_tot+PA_tot, 'c.') diff --git a/fluidsim/solvers/sw1l/output/spect_energy_budget.py b/fluidsim/solvers/sw1l/output/spect_energy_budget.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL291dHB1dC9zcGVjdF9lbmVyZ3lfYnVkZ2V0LnB5 --- /dev/null +++ b/fluidsim/solvers/sw1l/output/spect_energy_budget.py @@ -0,0 +1,936 @@ +import numpy as np +import h5py + +from fluiddyn.util import mpi + +from fluidsim.base.output.spect_energy_budget import ( + SpectralEnergyBudgetBase, cumsum_inv, inner_prod) + + +class SpectralEnergyBudgetSW1l(SpectralEnergyBudgetBase): + """Save and plot spectra.""" + + def __init__(self, output): + + params = output.sim.params + self.c2 = params.c2 + self.f = params.f + + super(SpectralEnergyBudgetSW1l, self).__init__(output) + + def compute(self): + """compute spectral energy budget the one time.""" + oper = self.sim.oper + + # print_memory_usage('start function compute seb') + + ux = self.sim.state.state_phys['ux'] + uy = self.sim.state.state_phys['uy'] + eta = self.sim.state.state_phys['eta'] + h = 1.+eta + + Jx = h*ux + Jy = h*uy + Jx_fft = oper.fft2(Jx) + Jy_fft = oper.fft2(Jy) + del(Jx, Jy) + + ux_fft = self.sim.state('ux_fft') + uy_fft = self.sim.state('uy_fft') + eta_fft = self.sim.state('eta_fft') + h_fft = eta_fft.copy() + if mpi.rank == 0: + h_fft[0, 0] = 1. + + rot_fft = oper.rotfft_from_vecfft(ux_fft, uy_fft) + urx_fft, ury_fft = oper.vecfft_from_rotfft(rot_fft) + del(rot_fft) + urx = oper.ifft2(urx_fft) + ury = oper.ifft2(ury_fft) + + q_fft, div_fft, a_fft = \ + self.oper.qdafft_from_uxuyetafft(ux_fft, uy_fft, eta_fft) + + # if self.params.f != 0: + # udx_fft, udy_fft = oper.vecfft_from_divfft(div_fft) + # ugx_fft, ugy_fft, etag_fft = \ + # self.oper.uxuyetafft_from_qfft(q_fft) + # uax_fft, uay_fft, etaa_fft = \ + # self.oper.uxuyetafft_from_afft(a_fft) + # del(a_fft) + # # velocity influenced by linear terms + # u_infl_lin_x = udx_fft + uax_fft + # u_infl_lin_y = udy_fft + uay_fft + + udx_fft, udy_fft = oper.vecfft_from_divfft(div_fft) + udx = oper.ifft2(udx_fft) + udy = oper.ifft2(udy_fft) + div = oper.ifft2(div_fft) + del(div_fft) + + # print_memory_usage('before starting computing fluxes') + + + # compute flux of Charney PE + Fq_fft = self.fnonlinfft_from_uxuy_funcfft(urx, ury, q_fft) + transferCPE_fft = inner_prod(q_fft, Fq_fft) + del(q_fft, Fq_fft) + transfer2D_CPE = self.spectrum2D_from_fft(transferCPE_fft) + del(transferCPE_fft) + +# print( +# ('sum(transfer2D_CPE) = {0:9.4e} ; sum(abs(transfer2D_CPE)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_CPE), +# np.sum(abs(transfer2D_CPE))) +# ) + + + + px_h_fft, py_h_fft = oper.gradfft_from_fft(eta_fft) + px_h = oper.ifft2(px_h_fft) + py_h = oper.ifft2(py_h_fft) + + F_rh = -urx*px_h - ury*py_h + F_dh = -udx*px_h - udy*py_h - h*div/2 + F_de = -udx*px_h - udy*py_h - eta*div/2 + del(px_h, py_h) + F_rh_fft = oper.fft2(F_rh) + F_dh_fft = oper.fft2(F_dh) + F_de_fft = oper.fft2(F_de) + del(F_rh, F_dh, F_de) + oper.dealiasing(F_rh_fft) + oper.dealiasing(F_dh_fft) + oper.dealiasing(F_de_fft) + + transferEAr_fft = self.c2*inner_prod(h_fft, F_rh_fft) + transferEPd_fft = self.c2*inner_prod(h_fft, F_dh_fft) + transferEAd_fft = self.c2*inner_prod(eta_fft, F_de_fft) + del(F_rh_fft, F_dh_fft, F_de_fft) + + transfer2D_EAr = self.spectrum2D_from_fft(transferEAr_fft) + transfer2D_EPd = self.spectrum2D_from_fft(transferEPd_fft) + transfer2D_EAd = self.spectrum2D_from_fft(transferEAd_fft) + del(transferEAr_fft, transferEPd_fft, transferEAd_fft) + + # print_memory_usage('after transfer2D_EAr') + +# print( +# ('sum(transfer2D_EAr) = {0:9.4e} ; sum(abs(transfer2D_EAr)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_EAr), +# np.sum(abs(transfer2D_EAr))) +# ) + +# print( +# ('sum(transfer2D_EAd) = {0:9.4e} ; sum(abs(transfer2D_EAd)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_EAd), +# np.sum(abs(transfer2D_EAd))) +# ) + + hdiv_fft = oper.fft2(h*div) + convP_fft = self.c2/2.*inner_prod(h_fft, hdiv_fft) + convP2D = self.spectrum2D_from_fft(convP_fft) + del(convP_fft, h_fft, hdiv_fft) + + EP = self.c2/2*h*h + EP_fft = oper.fft2(EP) + del(EP, h) + px_EP_fft, py_EP_fft = oper.gradfft_from_fft(EP_fft) + del(EP_fft) + + convK_fft = 1./2*( + - inner_prod(ux_fft, px_EP_fft) + - inner_prod(uy_fft, py_EP_fft) + - self.c2*inner_prod(Jx_fft, px_h_fft) + - self.c2*inner_prod(Jy_fft, py_h_fft) + ) + del(px_h_fft, py_h_fft, px_EP_fft, py_EP_fft) + convK2D = self.spectrum2D_from_fft(convK_fft) + del(convK_fft) + + # print_memory_usage('after convK2D') + +# print( +# ('sum(convP2D-convK2D)*deltakh = {0:9.4e}, sum(convP2D)*deltakh = {1:9.4e}' +# ).format( +# np.sum(convP2D-convK2D)*self.oper.deltakh, +# np.sum(convP2D)*self.oper.deltakh +# ) +# ) + +# print( +# (' sum(convK2D)*deltakh = {0:9.4e}' +# ).format( +# np.sum(convK2D)*self.oper.deltakh +# ) +# ) + + + Fxrd_fft, Fxdd_fft = self.fnonlinfft_from_uruddivfunc( + urx, ury, udx, udy, div, udx_fft, udx) + Fyrd_fft, Fydd_fft = self.fnonlinfft_from_uruddivfunc( + urx, ury, udx, udy, div, udy_fft, udy) + Fxrr_fft, Fxdr_fft = self.fnonlinfft_from_uruddivfunc( + urx, ury, udx, udy, div, urx_fft, urx) + Fyrr_fft, Fydr_fft = self.fnonlinfft_from_uruddivfunc( + urx, ury, udx, udy, div, ury_fft, ury) + + + transferErrr_fft = ( inner_prod(urx_fft, Fxrr_fft) + + inner_prod(ury_fft, Fyrr_fft) + ) + transfer2D_Errr = self.spectrum2D_from_fft(transferErrr_fft) + del(transferErrr_fft) +# print( +# ('sum(transfer2D_Errr) = {0:9.4e} ; sum(abs(transfer2D_Errr)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_Errr), +# np.sum(abs(transfer2D_Errr))) +# ) + + transferEdrd_fft = ( inner_prod(udx_fft, Fxrd_fft) + + inner_prod(udy_fft, Fyrd_fft) + ) + transfer2D_Edrd = self.spectrum2D_from_fft(transferEdrd_fft) + del(transferEdrd_fft) +# print( +# ('sum(transfer2D_Edrd) = {0:9.4e} ; sum(abs(transfer2D_Edrd)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_Edrd), +# np.sum(abs(transfer2D_Edrd))) +# ) + Clfromqq = ( inner_prod(udx_fft, Fxrr_fft) + + inner_prod(udy_fft, Fyrr_fft) + ) + transferEdrr_rrd_fft = ( Clfromqq + + inner_prod(urx_fft, Fxrd_fft) + + inner_prod(ury_fft, Fyrd_fft) + ) + Clfromqq = self.spectrum2D_from_fft(Clfromqq) + transfer2D_Edrr_rrd = self.spectrum2D_from_fft(transferEdrr_rrd_fft) + del(transferEdrr_rrd_fft) +# print( +# ('sum(transfer2D_Edrr_rrd) = {0:9.4e} ; ' +# 'sum(abs(transfer2D_Edrr_rrd)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_Edrr_rrd), +# np.sum(abs(transfer2D_Edrr_rrd))) +# ) + + + + transferErdr_fft = ( inner_prod(urx_fft, Fxdr_fft) + + inner_prod(ury_fft, Fydr_fft) + ) + transfer2D_Erdr = self.spectrum2D_from_fft(transferErdr_fft) + del(transferErdr_fft) +# print( +# ('sum(transfer2D_Erdr) = {0:9.4e} ; sum(abs(transfer2D_Erdr)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_Erdr), +# np.sum(abs(transfer2D_Erdr))) +# ) + + transferEddd_fft = ( inner_prod(udx_fft, Fxdd_fft) + + inner_prod(udy_fft, Fydd_fft) + ) + transfer2D_Eddd = self.spectrum2D_from_fft(transferEddd_fft) + del(transferEddd_fft) +# print( +# ('sum(transfer2D_Eddd) = {0:9.4e} ; sum(abs(transfer2D_Eddd)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_Eddd), +# np.sum(abs(transfer2D_Eddd))) +# ) + + Cqfromll = ( inner_prod(urx_fft, Fxdd_fft) + + inner_prod(ury_fft, Fydd_fft) + ) + + transferEddr_rdd_fft = ( Cqfromll + + inner_prod(udx_fft, Fxdr_fft) + + inner_prod(udy_fft, Fydr_fft) + ) + + Cqfromll = self.spectrum2D_from_fft(Cqfromll) + transfer2D_Eddr_rdd = self.spectrum2D_from_fft(transferEddr_rdd_fft) + del(transferEddr_rdd_fft) +# print( +# ('sum(transfer2D_Eddr_rdd) = {0:9.4e} ; ' +# 'sum(abs(transfer2D_Eddr_rdd)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_Eddr_rdd), +# np.sum(abs(transfer2D_Eddr_rdd))) +# ) + + + Fx_ru_fft = Fxrr_fft + Fxrd_fft + del(Fxrr_fft, Fxrd_fft) + Fy_ru_fft = Fyrr_fft + Fyrd_fft + del(Fyrr_fft, Fyrd_fft) + Fx_du_fft = Fxdr_fft + Fxdd_fft + del(Fxdr_fft, Fxdd_fft) + Fy_du_fft = Fydr_fft + Fydd_fft + del(Fydr_fft, Fydd_fft) + + + # print_memory_usage('after Fy_du_fft') + + + etaux = eta*ux + etauy = eta*uy + + etaux_fft = oper.fft2(etaux) + etauy_fft = oper.fft2(etauy) + del(etaux, etauy) + + px_etaux_fft, py_etaux_fft = oper.gradfft_from_fft(etaux_fft) + px_etauy_fft, py_etauy_fft = oper.gradfft_from_fft(etauy_fft) + + px_etaux = oper.ifft2(px_etaux_fft) + del(px_etaux_fft) + py_etaux = oper.ifft2(py_etaux_fft) + del(py_etaux_fft) + px_etauy = oper.ifft2(px_etauy_fft) + del(px_etauy_fft) + py_etauy = oper.ifft2(py_etauy_fft) + del(py_etauy_fft) + + Fx_reu = -urx*px_etaux - ury*py_etaux + Fx_reu_fft = oper.fft2(Fx_reu) + del(Fx_reu) + + Fx_deu = -udx*px_etaux - udy*py_etaux - 0.5*div*eta*ux + del(px_etaux, py_etaux) + Fx_deu_fft = oper.fft2(Fx_deu) + del(Fx_deu) + + Fy_reu = -urx*px_etauy - ury*py_etauy + Fy_reu_fft = oper.fft2(Fy_reu) + del(Fy_reu) + + Fy_deu = -udx*px_etauy - udy*py_etauy - 0.5*div*eta*uy + del(px_etauy, py_etauy) + Fy_deu_fft = oper.fft2(Fy_deu) + del(Fy_deu) + + + + transferEureu_fft = 0.5*( + inner_prod(ux_fft, Fx_reu_fft) + + inner_prod(uy_fft, Fy_reu_fft) + + inner_prod(etaux_fft, Fx_ru_fft) + + inner_prod(etauy_fft, Fy_ru_fft) + ) + del(Fx_reu_fft, Fy_reu_fft, Fx_ru_fft, Fy_ru_fft) + + transfer2D_Eureu = self.spectrum2D_from_fft(transferEureu_fft) +# print( +# ('sum(transferEureu_fft) = {0:9.4e} ; ' +# 'sum(abs(transferEureu_fft)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_Eureu), +# np.sum(abs(transfer2D_Eureu))) +# ) + + transferEudeu_fft = 0.5*( + inner_prod(ux_fft, Fx_deu_fft) + + inner_prod(uy_fft, Fy_deu_fft) + + inner_prod(etaux_fft, Fx_du_fft) + + inner_prod(etauy_fft, Fy_du_fft) + ) + del(Fx_deu_fft, Fy_deu_fft, Fx_du_fft, Fy_du_fft) + del(etaux_fft, etauy_fft) + + transfer2D_Eudeu = self.spectrum2D_from_fft(transferEudeu_fft) + del(transferEudeu_fft) +# print( +# ('sum(transferEudeu_fft) = {0:9.4e} ; ' +# 'sum(abs(transferEudeu_fft)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_Eudeu), +# np.sum(abs(transfer2D_Eudeu))) +# ) + + transfer2D_EKr = (transfer2D_Errr + transfer2D_Edrd + + transfer2D_Edrr_rrd + + transfer2D_Eureu + ) + transfer2D_EKd = (transfer2D_Erdr + transfer2D_Eddd + + transfer2D_Eddr_rdd + + transfer2D_Eudeu + ) + + # print_memory_usage('end of function compute seb') + + + dico_results = { + 'transfer2D_EAr': transfer2D_EAr, + 'transfer2D_EAd': transfer2D_EAd, + 'transfer2D_EPd': transfer2D_EPd, + 'transfer2D_EKr': transfer2D_EKr, + 'transfer2D_EKd': transfer2D_EKd, + 'transfer2D_Errr': transfer2D_Errr, + 'transfer2D_Edrd': transfer2D_Edrd, + 'Clfromqq': Clfromqq, + 'transfer2D_Edrr_rrd': transfer2D_Edrr_rrd, + 'transfer2D_Erdr': transfer2D_Erdr, + 'transfer2D_Eddd': transfer2D_Eddd, + 'Cqfromll': Cqfromll, + 'transfer2D_Eddr_rdd': transfer2D_Eddr_rdd, + 'transfer2D_Eureu': transfer2D_Eureu, + 'transfer2D_Eudeu': transfer2D_Eudeu, + 'convP2D': convP2D, + 'convK2D': convK2D, + 'transfer2D_CPE': transfer2D_CPE, +} + return dico_results + + + + + + def _online_plot(self, dico_results): + + transfer2D_CPE = dico_results['transfer2D_CPE'] + transfer2D_EKr = dico_results['transfer2D_EKr'] + transfer2D_EKd = dico_results['transfer2D_EKd'] + transfer2D_EK = transfer2D_EKr + transfer2D_EKd + transfer2D_EAr = dico_results['transfer2D_EAr'] + transfer2D_EAd = dico_results['transfer2D_EAd'] + transfer2D_EA = transfer2D_EAr + transfer2D_EAd + convP2D = dico_results['convP2D'] + convK2D = dico_results['convK2D'] + khE = self.oper.khE + PiCPE = cumsum_inv(transfer2D_CPE)*self.oper.deltakh + PiEK = cumsum_inv(transfer2D_EK)*self.oper.deltakh + PiEA = cumsum_inv(transfer2D_EA)*self.oper.deltakh + CCP = cumsum_inv(convP2D)*self.oper.deltakh + CCK = cumsum_inv(convK2D)*self.oper.deltakh + + self.axe_a.plot(khE+khE[1], PiEK, 'r') + self.axe_a.plot(khE+khE[1], PiEA, 'b') + self.axe_a.plot(khE+khE[1], CCP, 'y') + self.axe_a.plot(khE+khE[1], CCK, 'y--') + + self.axe_b.plot(khE+khE[1], PiCPE, 'g') + + + + + def plot(self, tmin=0, tmax=1000, delta_t=2): + + f = h5py.File(self.path_file, 'r') + + dset_times = f['times'] + times = dset_times[...] + # nt = len(times) + + dset_khE = f['khE'] + khE = dset_khE[...] + + dset_transfer2D_EKr = f['transfer2D_EKr'] + dset_transfer2D_EKd = f['transfer2D_EKd'] + dset_transfer2D_EAr = f['transfer2D_EAr'] + dset_transfer2D_EAd = f['transfer2D_EAd'] + dset_transfer2D_EPd = f['transfer2D_EPd'] + dset_convP2D = f['convP2D'] + dset_convK2D = f['convK2D'] + dset_transfer2D_CPE = f['transfer2D_CPE'] + + delta_t_save = np.mean(times[1:]-times[0:-1]) + delta_i_plot = int(np.round(delta_t/delta_t_save)) + if delta_i_plot == 0 and delta_t != 0.: + delta_i_plot = 1 + delta_t = delta_i_plot*delta_t_save + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + + + to_print = 'plot(tmin={0}, tmax={1}, delta_t={2:.2f})'.format( + tmin, tmax, delta_t) + print(to_print) + + to_print = '''plot fluxes 2D +tmin = {0:8.6g} ; tmax = {1:8.6g} ; delta_t = {2:8.6g} +imin = {3:8d} ; imax = {4:8d} ; delta_i = {5:8d}'''.format( +tmin_plot, tmax_plot, delta_t, +imin_plot, imax_plot, delta_i_plot) + print(to_print) + + + + + x_left_axe = 0.12 + z_bottom_axe = 0.36 + width_axe = 0.85 + height_axe = 0.57 + + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('$k_h$') + ax1.set_ylabel('transfers') + title = ('energy flux, solver '+self.output.name_solver+ +', nh = {0:5d}'.format(self.nx)+ +', c = {0:.4g}, f = {1:.4g}'.format(np.sqrt(self.c2), self.f) +) + ax1.set_title(title) + ax1.hold(True) + ax1.set_xscale('log') + ax1.set_yscale('linear') + + + khE = khE+1 + + if delta_t != 0.: + for it in xrange(imin_plot, imax_plot, delta_i_plot): + transferEKr = dset_transfer2D_EKr[it] + transferEAr = dset_transfer2D_EAr[it] + PiEKr = cumsum_inv(transferEKr)*self.oper.deltakh + PiEAr = cumsum_inv(transferEAr)*self.oper.deltakh + PiE = PiEKr + PiEAr + ax1.plot(khE, PiE, 'k', linewidth=1) + + convK = dset_convK2D[it] + CCK = cumsum_inv(convK)*self.oper.deltakh + ax1.plot(khE, CCK, 'y', linewidth=1) + + convP = dset_convP2D[it] + CCP = cumsum_inv(convP)*self.oper.deltakh + ax1.plot(khE, CCP, 'y--', linewidth=1) + + # print(convK.sum()*self.oper.deltakh, + # convP.sum()*self.oper.deltakh, + # CCP[0], CCK[0]) + + + + + transferEKr = dset_transfer2D_EKr[imin_plot:imax_plot].mean(0) + transferEKd = dset_transfer2D_EKd[imin_plot:imax_plot].mean(0) + transferEAr = dset_transfer2D_EAr[imin_plot:imax_plot].mean(0) + transferEAd = dset_transfer2D_EAd[imin_plot:imax_plot].mean(0) + transferEPd = dset_transfer2D_EPd[imin_plot:imax_plot].mean(0) + + + PiEKr = cumsum_inv(transferEKr)*self.oper.deltakh + PiEKd = cumsum_inv(transferEKd)*self.oper.deltakh + PiEAr = cumsum_inv(transferEAr)*self.oper.deltakh + PiEAd = cumsum_inv(transferEAd)*self.oper.deltakh + PiEPd = cumsum_inv(transferEPd)*self.oper.deltakh + + PiEK = PiEKr + PiEKd + PiEA = PiEAr + PiEAd + PiE = PiEK + PiEA + + ax1.plot(khE, PiE, 'k', linewidth=2) + ax1.plot(khE, PiEK, 'r', linewidth=2) + ax1.plot(khE, PiEA, 'b', linewidth=2) + + + + ax1.plot(khE, PiEKr, 'r--', linewidth=2) + ax1.plot(khE, PiEKd, 'r:', linewidth=2) + + + ax1.plot(khE, PiEAr, 'b--', linewidth=2) + ax1.plot(khE, PiEAd, 'b:', linewidth=2) + # ax1.plot(khE, PiEPd, 'c:', linewidth=1) + + + + + + convP = dset_convP2D[imin_plot:imax_plot].mean(0) + convK = dset_convK2D[imin_plot:imax_plot].mean(0) + + CCP = cumsum_inv(convP)*self.oper.deltakh + CCK = cumsum_inv(convK)*self.oper.deltakh + + ax1.plot(khE, CCP, 'y--', linewidth=2) + ax1.plot(khE, CCK, 'y', linewidth=2) + +# print(convK.sum()*self.oper.deltakh, +# convP.sum()*self.oper.deltakh, +# CCP[0], CCK[0], +# CCP[1], CCK[1] +# ) + + + + dset_transfer2D_Errr = f['transfer2D_Errr'] + dset_transfer2D_Edrd = f['transfer2D_Edrd'] + dset_transfer2D_Edrr_rrd = f['transfer2D_Edrr_rrd'] + + transferEdrr_rrd = \ + dset_transfer2D_Edrr_rrd[imin_plot:imax_plot].mean(0) + transferErrr = dset_transfer2D_Errr[imin_plot:imax_plot].mean(0) + transferEdrd = dset_transfer2D_Edrd[imin_plot:imax_plot].mean(0) + + Pi_drr_rrd = cumsum_inv(transferEdrr_rrd)*self.oper.deltakh + Pi_rrr = cumsum_inv(transferErrr)*self.oper.deltakh + Pi_drd = cumsum_inv(transferEdrd)*self.oper.deltakh + + ax1.plot(khE, Pi_drr_rrd, 'm:', linewidth=1) + ax1.plot(khE, Pi_rrr, 'm--', linewidth=1) + ax1.plot(khE, Pi_drd, 'm-.', linewidth=1) + + + + dset_transfer2D_Eddd = f['transfer2D_Eddd'] + dset_transfer2D_Erdr = f['transfer2D_Erdr'] + dset_transfer2D_Eddr_rdd = f['transfer2D_Eddr_rdd'] + dset_transfer2D_Eudeu = f['transfer2D_Eudeu'] + + + transferEddr_rdd = \ + dset_transfer2D_Eddr_rdd[imin_plot:imax_plot].mean(0) + transferEddd = dset_transfer2D_Eddd[imin_plot:imax_plot].mean(0) + transferErdr = dset_transfer2D_Erdr[imin_plot:imax_plot].mean(0) + + transferEudeu = dset_transfer2D_Eudeu[imin_plot:imax_plot].mean(0) + + Pi_ddr_rdd = cumsum_inv(transferEddr_rdd)*self.oper.deltakh + Pi_ddd = cumsum_inv(transferEddd)*self.oper.deltakh + Pi_rdr = cumsum_inv(transferErdr)*self.oper.deltakh + + Pi_udeu = cumsum_inv(transferEudeu)*self.oper.deltakh + + + ax1.plot(khE, Pi_ddr_rdd, 'c:', linewidth=1) + ax1.plot(khE, Pi_ddd, 'c--', linewidth=1) + ax1.plot(khE, Pi_rdr, 'c-.', linewidth=1) + + ax1.plot(khE, Pi_udeu, 'g', linewidth=1) + + + + + + z_bottom_axe = 0.07 + height_axe = 0.17 + size_axe[1] = z_bottom_axe + size_axe[3] = height_axe + ax2 = fig.add_axes(size_axe) + ax2.set_xlabel('$k_h$') + ax2.set_ylabel('transfers') + title = ('Charney PE flux') + ax2.set_title(title) + ax2.hold(True) + ax2.set_xscale('log') + ax2.set_yscale('linear') + + if delta_t != 0.: + for it in xrange(imin_plot,imax_plot+1,delta_i_plot): + transferCPE = dset_transfer2D_CPE[it] + PiCPE = cumsum_inv(transferCPE)*self.oper.deltakh + ax2.plot(khE, PiCPE, 'g', linewidth=1) + + transferCPE = dset_transfer2D_CPE[imin_plot:imax_plot].mean(0) + PiCPE = cumsum_inv(transferCPE)*self.oper.deltakh + + ax2.plot(khE, PiCPE, 'm', linewidth=2) + + f.close() + + + + +class SpectralEnergyBudgetMSW1l(SpectralEnergyBudgetSW1l): + """Save and plot spectra.""" + + + + + def compute(self): + """compute spectral energy budget the one time.""" + oper = self.sim.oper + + # ux = self.sim.state.state_phys['ux'] + # uy = self.sim.state.state_phys['uy'] + + ux_fft = self.sim.state.state_fft['ux_fft'] + uy_fft = self.sim.state.state_fft['uy_fft'] + eta_fft = self.sim.state.state_fft['eta_fft'] + + rot_fft = oper.rotfft_from_vecfft(ux_fft, uy_fft) + urx_fft, ury_fft = oper.vecfft_from_rotfft(rot_fft) + del(rot_fft) + urx = oper.ifft2(urx_fft) + ury = oper.ifft2(ury_fft) + + q_fft, div_fft, a_fft = \ + self.oper.qdafft_from_uxuyetafft(ux_fft, uy_fft, eta_fft) + + udx_fft, udy_fft = oper.vecfft_from_divfft(div_fft) + + if self.params.f != 0: + ugx_fft, ugy_fft, etag_fft = \ + self.oper.uxuyetafft_from_qfft(q_fft) + uax_fft, uay_fft, etaa_fft = \ + self.oper.uxuyetafft_from_afft(a_fft) + # velocity influenced by linear terms + u_infl_lin_x = udx_fft + uax_fft + u_infl_lin_y = udy_fft + uay_fft + + + + + # compute flux of Charney PE + Fq_fft = self.fnonlinfft_from_uxuy_funcfft(urx, ury, q_fft) + + transferCPE_fft = inner_prod(q_fft, Fq_fft) + del(q_fft, Fq_fft) + + transfer2D_CPE = self.spectrum2D_from_fft(transferCPE_fft) + del(transferCPE_fft) + +# print( +# ('sum(transfer2D_CPE) = {0:9.4e} ; sum(abs(transfer2D_CPE)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_CPE), +# np.sum(abs(transfer2D_CPE))) +# ) + + + Feta_fft = self.fnonlinfft_from_uxuy_funcfft(urx, ury, eta_fft) + transferEA_fft = self.c2*inner_prod(eta_fft, Feta_fft) + del(Feta_fft) + transfer2D_EA = self.spectrum2D_from_fft(transferEA_fft) + del(transferEA_fft) + +# print( +# ('sum(transfer2D_EA) = {0:9.4e} ; sum(abs(transfer2D_EA)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_EA), +# np.sum(abs(transfer2D_EA))) +# ) + + + convA_fft = self.c2*inner_prod(eta_fft, div_fft) + convA2D = self.spectrum2D_from_fft(convA_fft) + del(convA_fft) + + + Fxrr_fft = self.fnonlinfft_from_uxuy_funcfft(urx, ury, urx_fft) + Fyrr_fft = self.fnonlinfft_from_uxuy_funcfft(urx, ury, ury_fft) + + Fxrd_fft = self.fnonlinfft_from_uxuy_funcfft(urx, ury, udx_fft) + Fyrd_fft = self.fnonlinfft_from_uxuy_funcfft(urx, ury, udy_fft) + + transferErrr_fft = ( inner_prod(urx_fft, Fxrr_fft) + + inner_prod(ury_fft, Fyrr_fft) + ) + transfer2D_Errr = self.spectrum2D_from_fft(transferErrr_fft) + del(transferErrr_fft) +# print( +# ('sum(transfer2D_Errr) = {0:9.4e} ; sum(abs(transfer2D_Errr)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_Errr), +# np.sum(abs(transfer2D_Errr))) +# ) + + transferEdrd_fft = ( inner_prod(udx_fft, Fxrd_fft) + + inner_prod(udy_fft, Fyrd_fft) + ) + transfer2D_Edrd = self.spectrum2D_from_fft(transferEdrd_fft) + del(transferEdrd_fft) +# print( +# ('sum(transfer2D_Edrd) = {0:9.4e} ; sum(abs(transfer2D_Edrd)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_Edrd), +# np.sum(abs(transfer2D_Edrd))) +# ) + + + Clfromqq = ( inner_prod(udx_fft, Fxrr_fft) + + inner_prod(udy_fft, Fyrr_fft) + ) + transferEdrr_rrd_fft = ( Clfromqq + + inner_prod(urx_fft, Fxrd_fft) + + inner_prod(ury_fft, Fyrd_fft) + ) + Clfromqq = self.spectrum2D_from_fft(Clfromqq) + transfer2D_Edrr_rrd = self.spectrum2D_from_fft(transferEdrr_rrd_fft) + del(transferEdrr_rrd_fft) +# print( +# ('sum(transfer2D_Edrr_rrd) = {0:9.4e} ; ' +# 'sum(abs(transfer2D_Edrr_rrd)) = {1:9.4e}' +# ).format( +# np.sum(transfer2D_Edrr_rrd), +# np.sum(abs(transfer2D_Edrr_rrd))) +# ) + + transfer2D_EK = transfer2D_Errr + transfer2D_Edrd +transfer2D_Edrr_rrd + + dico_results = { + 'transfer2D_EK': transfer2D_EK, + 'transfer2D_Errr': transfer2D_Errr, + 'transfer2D_Edrd': transfer2D_Edrd, + 'Clfromqq': Clfromqq, + 'transfer2D_Edrr_rrd': transfer2D_Edrr_rrd, + 'transfer2D_EA': transfer2D_EA, + 'convA2D': convA2D, + 'transfer2D_CPE': transfer2D_CPE} + return dico_results + + + + + + def _online_plot(self, dico_results): + + transfer2D_CPE = dico_results['transfer2D_CPE'] + transfer2D_EK = dico_results['transfer2D_EK'] + transfer2D_EA = dico_results['transfer2D_EA'] + convA2D = dico_results['convA2D'] + khE = self.oper.khE + PiCPE = cumsum_inv(transfer2D_CPE)*self.oper.deltakh + PiEK = cumsum_inv(transfer2D_EK)*self.oper.deltakh + PiEA = cumsum_inv(transfer2D_EA)*self.oper.deltakh + CCA = cumsum_inv(convA2D)*self.oper.deltakh + self.axe_a.plot(khE+khE[1], PiEK, 'r') + self.axe_a.plot(khE+khE[1], PiEA, 'b') + self.axe_a.plot(khE+khE[1], CCA, 'y') + self.axe_b.plot(khE+khE[1], PiCPE, 'g') + + + def plot(self, tmin=0, tmax=1000, delta_t=2): + + f = h5py.File(self.path_file, 'r') + + dset_times = f['times'] + dset_khE = f['khE'] + khE = dset_khE[...] + # khE = khE+khE[1] + + dset_transfer2D_EK = f['transfer2D_EK'] + dset_transfer2D_Errr = f['transfer2D_Errr'] + dset_transfer2D_Edrd = f['transfer2D_Edrd'] + dset_transfer2D_Edrr_rrd = f['transfer2D_Edrr_rrd'] + dset_transfer2D_EA = f['transfer2D_EA'] + dset_convA2D = f['convA2D'] + dset_transfer2D_CPE = f['transfer2D_CPE'] + + times = dset_times[...] + nt = len(times) + + delta_t_save = np.mean(times[1:]-times[0:-1]) + delta_i_plot = int(np.round(delta_t/delta_t_save)) + if delta_i_plot == 0 and delta_t != 0.: + delta_i_plot=1 + delta_t = delta_i_plot*delta_t_save + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + + + to_print = 'plot(tmin={0}, tmax={1}, delta_t={2:.2f})'.format( + tmin, tmax, delta_t) + print(to_print) + + to_print = '''plot fluxes 2D +tmin = {0:8.6g} ; tmax = {1:8.6g} ; delta_t = {2:8.6g} +imin = {3:8d} ; imax = {4:8d} ; delta_i = {5:8d}'''.format( +tmin_plot, tmax_plot, delta_t, +imin_plot, imax_plot, delta_i_plot) + print(to_print) + + + + + x_left_axe = 0.12 + z_bottom_axe = 0.36 + width_axe = 0.85 + height_axe = 0.57 + + size_axe = [x_left_axe, z_bottom_axe, + width_axe, height_axe] + fig, ax1 = self.output.figure_axe(size_axe=size_axe) + ax1.set_xlabel('$k_h$') + ax1.set_ylabel('transfers') + title = ('energy flux, solver '+self.output.name_solver+ +', nh = {0:5d}'.format(self.nx)+ +', c = {0:.4g}, f = {1:.4g}'.format(np.sqrt(self.c2), self.f) +) + ax1.set_title(title) + ax1.hold(True) + ax1.set_xscale('log') + ax1.set_yscale('linear') + + if delta_t != 0.: + for it in xrange(imin_plot, imax_plot, delta_i_plot): + transferEK = dset_transfer2D_EK[it] + transferEA = dset_transfer2D_EA[it] + PiEK = cumsum_inv(transferEK)*self.oper.deltakh + PiEA = cumsum_inv(transferEA)*self.oper.deltakh + PiE = PiEK + PiEA + ax1.plot(khE, PiE, 'k', linewidth=1) + + transferEK = dset_transfer2D_EK[imin_plot:imax_plot].mean(0) + transferEA = dset_transfer2D_EA[imin_plot:imax_plot].mean(0) + PiEK = cumsum_inv(transferEK)*self.oper.deltakh + PiEA = cumsum_inv(transferEA)*self.oper.deltakh + PiE = PiEK + PiEA + + ax1.plot(khE, PiE, 'k', linewidth=2) + ax1.plot(khE, PiEK, 'r', linewidth=2) + ax1.plot(khE, PiEA, 'b', linewidth=2) + + + transferEdrr_rrd = \ + dset_transfer2D_Edrr_rrd[imin_plot:imax_plot].mean(0) + transferErrr = dset_transfer2D_Errr[imin_plot:imax_plot].mean(0) + transferEdrd = dset_transfer2D_Edrd[imin_plot:imax_plot].mean(0) + + Pi_drr_rrd = cumsum_inv(transferEdrr_rrd)*self.oper.deltakh + Pi_rrr = cumsum_inv(transferErrr)*self.oper.deltakh + Pi_drd = cumsum_inv(transferEdrd)*self.oper.deltakh + + ax1.plot(khE, Pi_drr_rrd, 'm:', linewidth=1) + ax1.plot(khE, Pi_rrr, 'm--', linewidth=1) + ax1.plot(khE, Pi_drd, 'm-.', linewidth=1) + + + + + convA2D = dset_convA2D[imin_plot:imax_plot].mean(0) + CCA = cumsum_inv(convA2D)*self.oper.deltakh + + ax1.plot(khE+khE[1], CCA, 'y', linewidth=2) + + z_bottom_axe = 0.07 + height_axe = 0.17 + size_axe[1] = z_bottom_axe + size_axe[3] = height_axe + ax2 = fig.add_axes(size_axe) + ax2.set_xlabel('$k_h$') + ax2.set_ylabel('transfers') + title = ('Charney PE flux') + ax2.set_title(title) + ax2.hold(True) + ax2.set_xscale('log') + ax2.set_yscale('linear') + + if delta_t != 0.: + for it in xrange(imin_plot, imax_plot+1, delta_i_plot): + transferCPE = dset_transfer2D_CPE[it] + PiCPE = cumsum_inv(transferCPE)*self.oper.deltakh + ax2.plot(khE, PiCPE, 'g', linewidth=1) + + transferCPE = dset_transfer2D_CPE[imin_plot:imax_plot].mean(0) + PiCPE = cumsum_inv(transferCPE)*self.oper.deltakh + + ax2.plot(khE, PiCPE, 'm', linewidth=2) + + diff --git a/fluidsim/solvers/sw1l/output/spectra.py b/fluidsim/solvers/sw1l/output/spectra.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL291dHB1dC9zcGVjdHJhLnB5 --- /dev/null +++ b/fluidsim/solvers/sw1l/output/spectra.py @@ -0,0 +1,351 @@ +import h5py + +import numpy as np + +from fluidsim.base.output.spectra import Spectra + + +class SpectraSW1l(Spectra): + """Save and plot spectra.""" + + + def __init__(self, output): + params = output.sim.params + self.c2 = params.c2 + self.f = params.f + + super(SpectraSW1l, self).__init__(output) + + + + def init_online_plot(self): + fig, axe = self.output.figure_axe(numfig=1000000) + self.axe = axe + axe.set_xlabel('k_h') + axe.set_ylabel('E(k_h)') + title = ('spectra, solver '+self.output.name_solver+ + ', nh = {0:5d}'.format(self.nx)+ + ', c = {0:.4g}, f = {1:.4g}'.format(np.sqrt(self.c2), self.f) + ) + axe.set_title(title) + axe.hold(True) + + + def compute(self): + """compute the values at one time.""" + # compute 'quantities_fft' + energyK_fft, energyA_fft, energyKr_fft = ( + self.output.compute_energies_fft()) + ErtelPE_fft, CharneyPE_fft = self.output.compute_PE_fft() + + energy_glin_fft, energy_dlin_fft, energy_alin_fft = \ + self.output.compute_lin_energies_fft() + + + + # compute the spectra 1D + spectrum1Dkx_EK, spectrum1Dky_EK = \ + self.spectra1D_from_fft(energyK_fft) + spectrum1Dkx_EA, spectrum1Dky_EA = \ + self.spectra1D_from_fft(energyA_fft) + spectrum1Dkx_EKr, spectrum1Dky_EKr = \ + self.spectra1D_from_fft(energyKr_fft) + spectrum1Dkx_EPE, spectrum1Dky_EPE = \ + self.spectra1D_from_fft(ErtelPE_fft) + spectrum1Dkx_CPE, spectrum1Dky_CPE = \ + self.spectra1D_from_fft(CharneyPE_fft) + spectrum1Dkx_Eglin, spectrum1Dky_Eglin = \ + self.spectra1D_from_fft(energy_glin_fft) + spectrum1Dkx_Edlin, spectrum1Dky_Edlin = \ + self.spectra1D_from_fft(energy_dlin_fft) + spectrum1Dkx_Ealin, spectrum1Dky_Ealin = \ + self.spectra1D_from_fft(energy_alin_fft) + + dico_spectra1D = { + 'spectrum1Dkx_EK': spectrum1Dkx_EK, + 'spectrum1Dky_EK': spectrum1Dky_EK, + 'spectrum1Dkx_EA': spectrum1Dkx_EA, + 'spectrum1Dky_EA': spectrum1Dky_EA, + 'spectrum1Dkx_EKr': spectrum1Dkx_EKr, + 'spectrum1Dky_EKr': spectrum1Dky_EKr, + 'spectrum1Dkx_EPE': spectrum1Dkx_EPE, + 'spectrum1Dky_EPE': spectrum1Dky_EPE, + 'spectrum1Dkx_CPE': spectrum1Dkx_CPE, + 'spectrum1Dky_CPE': spectrum1Dky_CPE, + 'spectrum1Dkx_Eglin': spectrum1Dkx_Eglin, + 'spectrum1Dky_Eglin': spectrum1Dky_Eglin, + 'spectrum1Dkx_Edlin': spectrum1Dkx_Edlin, + 'spectrum1Dky_Edlin': spectrum1Dky_Edlin, + 'spectrum1Dkx_Ealin': spectrum1Dkx_Ealin, + 'spectrum1Dky_Ealin': spectrum1Dky_Ealin} + + # compute the spectra 2D + spectrum2D_EK = self.spectrum2D_from_fft(energyK_fft) + spectrum2D_EA = self.spectrum2D_from_fft(energyA_fft) + spectrum2D_EKr = self.spectrum2D_from_fft(energyKr_fft) + spectrum2D_EPE = self.spectrum2D_from_fft(ErtelPE_fft) + spectrum2D_CPE = self.spectrum2D_from_fft(CharneyPE_fft) + spectrum2D_Eglin = self.spectrum2D_from_fft(energy_glin_fft) + spectrum2D_Edlin = self.spectrum2D_from_fft(energy_dlin_fft) + spectrum2D_Ealin = self.spectrum2D_from_fft(energy_alin_fft) + + dico_spectra2D = { + 'spectrum2D_EK': spectrum2D_EK, + 'spectrum2D_EA': spectrum2D_EA, + 'spectrum2D_EKr': spectrum2D_EKr, + 'spectrum2D_EPE': spectrum2D_EPE, + 'spectrum2D_CPE': spectrum2D_CPE, + 'spectrum2D_Eglin': spectrum2D_Eglin, + 'spectrum2D_Edlin': spectrum2D_Edlin, + 'spectrum2D_Ealin': spectrum2D_Ealin} + + return dico_spectra1D, dico_spectra2D + + + + def _online_plot(self, dico_spectra1D, dico_spectra2D): + if (self.params.oper.nx==self.params.oper.ny + and self.params.oper.Lx==self.params.oper.Ly): + spectrum2D_EK = dico_spectra2D['spectrum2D_EK'] + spectrum2D_EA = dico_spectra2D['spectrum2D_EA'] + spectrum2D_EKr = dico_spectra2D['spectrum2D_EKr'] + spectrum2D_E = spectrum2D_EK + spectrum2D_EA + spectrum2D_EKd = spectrum2D_EK - spectrum2D_EKr + khE = self.oper.khE + coef_norm = khE**(3.) + self.axe.loglog(khE, spectrum2D_E*coef_norm, 'k') + self.axe.loglog(khE, spectrum2D_EK*coef_norm, 'r') + self.axe.loglog(khE, spectrum2D_EA*coef_norm, 'b') + self.axe.loglog(khE, spectrum2D_EKr*coef_norm, 'r--') + self.axe.loglog(khE, spectrum2D_EKd*coef_norm, 'r:') + lin_inf, lin_sup = self.axe.get_ylim() + if lin_inf<10e-6: + lin_inf=10e-6 + self.axe.set_ylim([lin_inf, lin_sup]) + else: + print('you need to implement the ploting ' + 'of the spectra for this case') + + + + + + def plot1D(self, tmin=0, tmax=1000, delta_t=2, + coef_compensate=3): + + f = h5py.File(self.path_file1D, 'r') + dset_times = f['times'] + times = dset_times[...] + # nb_spectra = times.shape[0] + + dset_kxE = f['kxE'] + # dset_kyE = f['kyE'] + + kh = dset_kxE[...] + + dset_spectrum1Dkx_EK = f['spectrum1Dkx_EK'] + dset_spectrum1Dky_EK = f['spectrum1Dky_EK'] + dset_spectrum1Dkx_EA = f['spectrum1Dkx_EA'] + dset_spectrum1Dky_EA = f['spectrum1Dky_EA'] + + dset_spectrum1Dkx_EKr = f['spectrum1Dkx_EKr'] + dset_spectrum1Dky_EKr = f['spectrum1Dky_EKr'] + + nt = len(times) + + delta_t_save = np.mean(times[1:]-times[0:-1]) + delta_i_plot = int(np.round(delta_t/delta_t_save)) + if delta_i_plot == 0 and delta_t != 0.: + delta_i_plot=1 + delta_t = delta_i_plot*delta_t_save + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + + to_print = ( +'plot1D(tmin={0}, tmax={1}, delta_t={2:.2f},'.format(tmin, tmax, delta_t) ++' coef_compensate={0:.3f})'.format(coef_compensate) +) + print(to_print) + + to_print = '''plot 1D spectra +tmin = {0:8.6g} ; tmax = {1:8.6g} ; delta_t = {2:8.6g} +imin = {3:8d} ; imax = {4:8d} ; delta_i = {5:8d}'''.format( +tmin_plot, tmax_plot, delta_t, +imin_plot, imax_plot, delta_i_plot) + print(to_print) + + fig, ax1 = self.output.figure_axe() + ax1.set_xlabel('$k_h$') + ax1.set_ylabel('1D spectra') + title = ('1D spectra, solver '+self.output.name_solver+ +', nh = {0:5d}'.format(self.nx)+ +', c = {0:.4g}, f = {1:.4g}'.format(np.sqrt(self.c2), self.f) +) + ax1.set_title(title) + ax1.hold(True) + ax1.set_xscale('log') + ax1.set_yscale('log') + + coef_norm = kh**(coef_compensate) + + min_to_plot = 1e-16 + + if delta_t != 0.: + for it in xrange(imin_plot, imax_plot+1, delta_i_plot): + E_K = (dset_spectrum1Dkx_EK[it]+dset_spectrum1Dky_EK[it]) + # E_K[E_K<min_to_plot] = 0. + E_A = (dset_spectrum1Dkx_EA[it]+dset_spectrum1Dky_EA[it]) + # E_A[E_A<min_to_plot] = 0. + E_tot = E_K + E_A + + E_Kr = (dset_spectrum1Dkx_EKr[it]+dset_spectrum1Dky_EKr[it]) + # E_Kr[E_Kr<min_to_plot] = 0. + E_Kd = E_K - E_Kr + + ax1.plot(kh, E_tot*coef_norm, 'k', linewidth=2) + ax1.plot(kh, E_K*coef_norm, 'r', linewidth=1) + ax1.plot(kh, E_A*coef_norm, 'b', linewidth=1) + ax1.plot(kh, E_Kr*coef_norm, 'r--', linewidth=1) + ax1.plot(kh, E_Kd*coef_norm, 'r:', linewidth=1) + + + E_K = (dset_spectrum1Dkx_EK[imin_plot:imax_plot+1] + +dset_spectrum1Dky_EK[imin_plot:imax_plot+1]).mean(0) + + E_A = (dset_spectrum1Dkx_EA[imin_plot:imax_plot+1] + +dset_spectrum1Dky_EA[imin_plot:imax_plot+1]).mean(0) + + ax1.plot(kh, E_K*coef_norm, 'r', linewidth=2) + ax1.plot(kh, E_A*coef_norm, 'b', linewidth=2) + + ax1.plot(kh, kh**(-3)*coef_norm, 'k', linewidth=1) + ax1.plot(kh, 0.01*kh**(-5./3)*coef_norm, 'k--', linewidth=1) + + + + + + + def plot2D(self, tmin=0, tmax=1000, delta_t=2, + coef_compensate=3): + + f = h5py.File(self.path_file2D, 'r') + dset_times = f['times'] + nb_spectra = dset_times.shape[0] + times = dset_times[...] + nt = len(times) + + dset_khE = f['khE'] + kh = dset_khE[...] + + dset_spectrumEK = f['spectrum2D_EK'] + dset_spectrumEA = f['spectrum2D_EA'] + dset_spectrumEKr = f['spectrum2D_EKr'] + + delta_t_save = np.mean(times[1:]-times[0:-1]) + delta_i_plot = int(np.round(delta_t/delta_t_save)) + if delta_i_plot == 0 and delta_t != 0.: + delta_i_plot=1 + delta_t = delta_i_plot*delta_t_save + + imin_plot = np.argmin(abs(times-tmin)) + imax_plot = np.argmin(abs(times-tmax)) + + tmin_plot = times[imin_plot] + tmax_plot = times[imax_plot] + + to_print = ( +'plot2D(tmin={0}, tmax={1}, delta_t={2:.2f},'.format(tmin, tmax, delta_t) ++' coef_compensate={0:.3f})'.format(coef_compensate) +) + print(to_print) + + to_print = '''plot 2D spectra +tmin = {0:8.6g} ; tmax = {1:8.6g} ; delta_t = {2:8.6g} +imin = {3:8d} ; imax = {4:8d} ; delta_i = {5:8d}'''.format( +tmin_plot, tmax_plot, delta_t, +imin_plot, imax_plot, delta_i_plot) + print(to_print) + + fig, ax1 = self.output.figure_axe() + ax1.set_xlabel('$k_h$') + ax1.set_ylabel('2D spectra') + title = ('2D spectra, solver '+self.output.name_solver+ +', nh = {0:5d}'.format(self.nx)+ +', c = {0:.4g}, f = {1:.4g}'.format(np.sqrt(self.c2), self.f) +) + ax1.set_title(title) + ax1.hold(True) + ax1.set_xscale('log') + ax1.set_yscale('log') + + coef_norm = kh**coef_compensate + + if delta_t != 0.: + for it in xrange(imin_plot, imax_plot+1, delta_i_plot): + EK = dset_spectrumEK[it] + EA = dset_spectrumEA[it] + EKr = dset_spectrumEKr[it] + + EK[EK<10e-16] = 0. + EA[EA<10e-16] = 0. + EKr[EKr<10e-16] = 0. + + E_tot = EK + EA + EKd = EK - EKr + + ax1.plot(kh, E_tot*coef_norm, 'k', linewidth=1) + ax1.plot(kh, EK*coef_norm, 'r', linewidth=1) + ax1.plot(kh, EA*coef_norm, 'b', linewidth=1) + ax1.plot(kh, EKr*coef_norm, 'r--', linewidth=1) + ax1.plot(kh, EKd*coef_norm, 'r:', linewidth=1) + + EK = dset_spectrumEK[imin_plot:imax_plot+1].mean(0) + EA = dset_spectrumEA[imin_plot:imax_plot+1].mean(0) + EKr = dset_spectrumEKr[imin_plot:imax_plot+1].mean(0) + + + EK[abs(EK)<10e-16] = 0. + EA[abs(EA)<10e-16] = 0. + EKr[abs(EKr)<10e-16] = 0. + + E_tot = EK + EA + EKd = EK - EKr + + + ax1.plot(kh, E_tot*coef_norm, 'k', linewidth=4) + ax1.plot(kh, EK*coef_norm, 'r', linewidth=2) + ax1.plot(kh, EA*coef_norm, 'b', linewidth=2) + ax1.plot(kh, EKr*coef_norm, 'r--', linewidth=2) + ax1.plot(kh, EKd*coef_norm, 'r:', linewidth=2) + + ax1.plot(kh, -EK*coef_norm, 'm', linewidth=2) + ax1.plot(kh, -EKd*coef_norm, 'm:', linewidth=2) + + + + + + if self.sim.info.solver.short_name.startswith('SW1l'): + dset_spectrumEdlin = f['spectrum2D_Edlin'] + Edlin = dset_spectrumEdlin[imin_plot:imax_plot+1].mean(0) + ax1.plot(kh, Edlin*coef_norm, 'y:', linewidth=1) + + if self.params.f != 0: + dset_spectrumEglin = f['spectrum2D_Eglin'] + Eglin = dset_spectrumEglin[imin_plot:imax_plot+1].mean(0) + ax1.plot(kh, Eglin*coef_norm, 'c', linewidth=1) + + dset_spectrumEalin = f['spectrum2D_Ealin'] + Ealin = dset_spectrumEalin[imin_plot:imax_plot+1].mean(0) + ax1.plot(kh, Ealin*coef_norm, 'y', linewidth=1) + + + + + ax1.plot(kh, kh**(-3)*coef_norm, 'k--', linewidth=1) + ax1.plot(kh, 0.01*kh**(-5./3)*coef_norm, 'k-.', linewidth=1) diff --git a/fluidsim/solvers/sw1l/solver.py b/fluidsim/solvers/sw1l/solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL3NvbHZlci5weQ== --- /dev/null +++ b/fluidsim/solvers/sw1l/solver.py @@ -0,0 +1,169 @@ +"""Solver one-layer shallow-water (Saint Venant) equations. +=========================================================== + +""" + +from fluidsim.operators.setofvariables import SetOfVariables +from fluidsim.base.solvers.pseudo_spect import ( + SimulBasePseudoSpectral, InfoSolverPseudoSpectral) + +from fluiddyn.util import mpi + + +class InfoSolverSW1l(InfoSolverPseudoSpectral): + """Information about the solver SW1l.""" + def __init__(self, **kargs): + super(InfoSolverSW1l, self).__init__(**kargs) + + if 'tag' in kargs and kargs['tag'] == 'solver': + + package = 'fluidsim.solvers.sw1l' + + self.module_name = package + '.solver' + self.class_name = 'Simul' + self.short_name = 'SW1l' + + classes = self.classes + + classes.State.module_name = package + '.state' + classes.State.class_name = 'StateSW1l' + + classes.InitFields.module_name = package + '.init_fields' + classes.InitFields.class_name = 'InitFieldsSW1l' + + classes.Output.module_name = package + '.output' + classes.Output.class_name = 'OutputSW1l' + + classes.Forcing.module_name = package + '.forcing' + classes.Forcing.class_name = 'ForcingSW1l' + + +info_solver = InfoSolverSW1l(tag='solver') +info_solver.complete_with_classes() + + +class Simul(SimulBasePseudoSpectral): + """A solver of the shallow-water 1 layer equations (SW1l)""" + + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + SimulBasePseudoSpectral._complete_params_with_default(params) + + attribs = {'f': 0, + 'c2': 20, + 'kd2': 0} + params.set_attribs(attribs) + + def __init__(self, params, info_solver=info_solver): + # Parameter(s) specific to this solver + params.kd2 = params.f**2/params.c2 + + # first, the common initialisations + super(Simul, self).__init__(params, info_solver) + + if mpi.rank == 0: + self.output.print_stdout( + 'c2 = {0:6.5g} ; f = {1:6.5g} ; kd2 = {2:6.5g}'.format( + params.c2, params.f, params.kd2)) + + def tendencies_nonlin(self, state_fft=None): + oper = self.oper + fft2 = oper.fft2 + + if state_fft is None: + state_phys = self.state.state_phys + else: + state_phys = self.state.return_statephys_from_statefft(state_fft) + + ux = state_phys['ux'] + uy = state_phys['uy'] + eta = state_phys['eta'] + rot = state_phys['rot'] + rot_abs = rot + self.params.f + + F1x = +rot_abs*uy + F1y = -rot_abs*ux + gradx_fft, grady_fft = oper.gradfft_from_fft( + fft2(self.params.c2*eta + (ux**2+uy**2)/2)) + oper.dealiasing(gradx_fft, grady_fft) + Fx_fft = fft2(F1x) - gradx_fft + Fy_fft = fft2(F1y) - grady_fft + + Feta_fft = -oper.divfft_from_vecfft(fft2((eta+1)*ux), + fft2((eta+1)*uy)) + oper.dealiasing(Fx_fft, Fy_fft, Feta_fft) + + # # for verification conservation energy + # Fx = oper.ifft2(Fx_fft) + # Fy = oper.ifft2(Fy_fft) + # Feta = oper.ifft2(Feta_fft) + # A = ( Feta*(ux**2+uy**2)/2 + # + (1+eta)*(ux*Fx+uy*Fy) + # + self.params.c2*eta*Feta ) + # A_fft = fft2(A) + # if mpi.rank == 0: + # print 'should be zero =', A_fft[0,0] + + tendencies_fft = SetOfVariables( + like_this_sov=self.state.state_fft, + name_type_variables='tendencies_nonlin' + ) + tendencies_fft['ux_fft'] = Fx_fft + tendencies_fft['uy_fft'] = Fy_fft + tendencies_fft['eta_fft'] = Feta_fft + + if self.params.FORCING: + tendencies_fft += self.forcing.get_forcing() + + return tendencies_fft + + +if __name__ == "__main__": + + import numpy as np + + import fluiddyn as fld + + params = fld.simul.create_params(info_solver) + + params.short_name_type_run = 'test' + + nh = 32 + Lh = 2*np.pi + params.oper.nx = nh + params.oper.ny = nh + params.oper.Lx = Lh + params.oper.Ly = Lh + + delta_x = params.oper.Lx/params.oper.nx + params.nu_8 = 2.*10e-1*params.forcing.forcing_rate**(1./3)*delta_x**8 + + params.time_stepping.t_end = 2. + + params.init_fields.type_flow_init = 'NOISE' + + params.FORCING = False + params.forcing.type = 'Random' + + params.output.periods_print.print_stdout = 0.25 + + params.output.periods_save.phys_fields = 0.5 + params.output.periods_save.spectra = 0.5 + params.output.periods_save.spect_energy_budg = 0.5 + params.output.periods_save.increments = 0.5 + params.output.periods_save.pdf = 0.5 + params.output.periods_save.time_signals_fft = True + + params.output.periods_plot.phys_fields = 0. + + params.output.phys_fields.field_to_plot = 'rot' + + sim = Simul(params) + + # sim.output.phys_fields.plot() + sim.time_stepping.start() + # sim.output.phys_fields.plot() + + fld.show() diff --git a/fluidsim/solvers/sw1l/state.py b/fluidsim/solvers/sw1l/state.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy9zdzFsL3N0YXRlLnB5 --- /dev/null +++ b/fluidsim/solvers/sw1l/state.py @@ -0,0 +1,138 @@ +""" +The module :mod:`stateSW1l` supplies the class :class:`StateSW1l`. +""" + +import numpy as np + +from fluidsim.operators.setofvariables import SetOfVariables +from fluidsim.base.state import StatePseudoSpectral + +from fluiddyn.util import mpi + + +class StateSW1l(StatePseudoSpectral): + """ + The class :class:`StateSW1l` contains the variables corresponding + to the state and handles the access to other fields for the solver + SW1l. + """ + + @staticmethod + def _complete_info_solver(info_solver): + """Complete the ContainerXML info_solver. + + This is a static method! + """ + info_solver.classes.State.set_attribs({ + 'keys_state_fft': ['ux_fft', 'uy_fft', 'eta_fft'], + 'keys_state_phys': ['ux', 'uy', 'eta', 'rot'], + 'keys_computable': [], + 'keys_phys_needed': ['ux', 'uy', 'eta'], + 'keys_linear_eigenmodes': ['q_fft', 'a_fft', 'd_fft']}) + + + def compute(self, key, SAVE_IN_DICT=True, RAISE_ERROR=True): + it = self.sim.time_stepping.it + if (key in self.vars_computed and it == self.it_computed[key]): + return self.vars_computed[key] + + if key == 'Jx': + ux = self.state_phys['ux'] + eta = self.state_phys['eta'] + h = 1 + eta + result = h*ux + elif key == 'Jy': + uy = self.state_phys['uy'] + eta = self.state_phys['eta'] + h = 1 + eta + result = h*uy + elif key == 'Jx_fft': + Jx = self.compute('Jx') + result = self.oper.fft2(Jx) + elif key == 'Jy_fft': + Jy = self.compute('Jy') + result = self.oper.fft2(Jy) + elif key == 'rot_fft': + ux_fft = self.state_fft['ux_fft'] + uy_fft = self.state_fft['uy_fft'] + result = self.oper.rotfft_from_vecfft(ux_fft, uy_fft) + elif key == 'div_fft': + ux_fft = self.state_fft['ux_fft'] + uy_fft = self.state_fft['uy_fft'] + result = self.oper.divfft_from_vecfft(ux_fft, uy_fft) + elif key == 'div': + div_fft = self.compute('div_fft') + result = self.oper.ifft2(div_fft) + elif key == 'q': + rot = self.state_phys['rot'] + eta = self.state_phys['eta'] + result = rot-self.param.f*eta + elif key == 'h': + eta = self.state_phys['eta'] + result = 1 + eta + + elif key == 'Floc': + h = self.compute('h') + ux = self.state_phys['ux'] + uy = self.state_phys['uy'] + result = np.sqrt((ux**2 + uy**2)/(self.sim.param.c2*h)) + + else: + to_print = 'Do not know how to compute "'+key+'".' + if RAISE_ERROR: + raise ValueError(to_print) + else: + if mpi.rank == 0: + print(to_print + +'\nreturn an array of zeros.') + + result = self.oper.constant_arrayX(value=0.) + + if SAVE_IN_DICT: + self.vars_computed[key] = result + self.it_computed[key] = it + + return result + + + + + def statefft_from_statephys(self): + """Compute the state in Fourier space.""" + ux = self.state_phys['ux'] + uy = self.state_phys['uy'] + eta = self.state_phys['eta'] + self.state_fft['ux_fft'] = self.oper.fft2(ux) + self.state_fft['uy_fft'] = self.oper.fft2(uy) + self.state_fft['eta_fft'] = self.oper.fft2(eta) + + def statephys_from_statefft(self): + """Compute the state in physical space.""" + ifft2 = self.oper.ifft2 + ux_fft = self.state_fft['ux_fft'] + uy_fft = self.state_fft['uy_fft'] + eta_fft = self.state_fft['eta_fft'] + self.state_phys['ux'] = ifft2(ux_fft) + self.state_phys['uy'] = ifft2(uy_fft) + self.state_phys['eta'] = ifft2(eta_fft) + rot_fft = self.oper.rotfft_from_vecfft(ux_fft, uy_fft) + self.state_phys['rot'] = ifft2(rot_fft) + + def return_statephys_from_statefft(self, state_fft=None): + """Return the state in physical space.""" + ifft2 = self.oper.ifft2 + if state_fft is None: + state_fft = self.state_fft + ux_fft = state_fft['ux_fft'] + uy_fft = state_fft['uy_fft'] + eta_fft = state_fft['eta_fft'] + state_phys = SetOfVariables(like_this_sov=self.state_phys) + state_phys['ux'] = ifft2(ux_fft) + state_phys['uy'] = ifft2(uy_fft) + state_phys['eta'] = ifft2(eta_fft) + + rot_fft = self.oper.rotfft_from_vecfft(ux_fft, uy_fft) + state_phys['rot'] = ifft2(rot_fft) + + return state_phys + diff --git a/fluidsim/solvers/test/__init__.py b/fluidsim/solvers/test/__init__.py new file mode 100644 diff --git a/fluidsim/solvers/test/test_solvers.py b/fluidsim/solvers/test/test_solvers.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy90ZXN0L3Rlc3Rfc29sdmVycy5weQ== --- /dev/null +++ b/fluidsim/solvers/test/test_solvers.py @@ -0,0 +1,65 @@ + +import unittest +import shutil + +import fluiddyn as fld + +from fluiddyn.io import stdout_redirected + + +def run_mini_simul(key_solver): + + solver = fld.simul.import_module_solver_from_key(key_solver) + params = fld.simul.create_params(solver) + + params.short_name_type_run = 'test' + + nh = 64 + params.oper.nx = nh + params.oper.ny = nh + Lh = 6. + params.oper.Lx = Lh + params.oper.Ly = Lh + + params.oper.coef_dealiasing = 2./3 + params.nu_8 = 2. + + try: + params.f = 1. + params.c2 = 200. + except KeyError: + pass + + params.time_stepping.t_end = 0.5 + + params.init_fields.type_flow_init = 'DIPOLE' + params.output.HAS_TO_SAVE = False + + with stdout_redirected(): + sim = solver.Simul(params) + sim.time_stepping.start() + + # clean by removing the directory + shutil.rmtree(sim.output.path_run) + + +class TestSolvers(unittest.TestCase): + def test_ns2d(self): + """Should be able to run a base experiment.""" + run_mini_simul('NS2D') + + def test_sw1l(self): + """Should be able to run a base experiment.""" + run_mini_simul('SW1l') + + def test_sw1l_onlywaves(self): + """Should be able to run a base experiment.""" + run_mini_simul('SW1l.onlywaves') + + def test_sw1l_exactlin(self): + """Should be able to run a base experiment.""" + run_mini_simul('SW1l.exactlin') + + +if __name__ == '__main__': + unittest.main() diff --git a/fluidsim/solvers/waves2d/__init__.py b/fluidsim/solvers/waves2d/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy93YXZlczJkL19faW5pdF9fLnB5 --- /dev/null +++ b/fluidsim/solvers/waves2d/__init__.py @@ -0,0 +1,13 @@ +"""2D waves solvers (:mod:`fluidsim.solvers.waves2d`) +=========================================================== + +.. currentmodule:: fluidsim.solvers.waves2d + +Provides: + +.. autosummary:: + :toctree: + + solver + +""" diff --git a/fluidsim/solvers/waves2d/solver.py b/fluidsim/solvers/waves2d/solver.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vc29sdmVycy93YXZlczJkL3NvbHZlci5weQ== --- /dev/null +++ b/fluidsim/solvers/waves2d/solver.py @@ -0,0 +1,187 @@ +"""2D waves solver (:mod:`fluidsim.solvers.waves2d.solver`) +================================================================== + +.. autoclass:: Simul + :members: + :private-members: + +""" + +from fluidsim.operators.setofvariables import SetOfVariables + +from fluidsim.base.solvers.pseudo_spect import ( + SimulBasePseudoSpectral, InfoSolverPseudoSpectral) + + +info_solver = InfoSolverPseudoSpectral() + +package = 'fluidsim.solvers.waves2d' +info_solver.module_name = package + '.solver' +info_solver.class_name = 'Simul' +info_solver.short_name = 'Waves2d' + +classes = info_solver.classes + +# classes.State.module_name = package + '.state' +# classes.State.class_name = 'StateWaves' + +# classes.InitFields.module_name = package + '.init_fields' +# classes.InitFields.class_name = 'InitFieldsWaves' + +# classes.Output.module_name = package + '.output' +# classes.Output.class_name = 'Output' + +# classes.Forcing.module_name = package + '.forcing' +# classes.Forcing.class_name = 'ForcingNS2D' + + +info_solver.complete_with_classes() + + +class Simul(SimulBasePseudoSpectral): + r"""Pseudo-spectral solver for equations of 2D waves. + + Notes + ----- + + .. |p| mathmacro:: \partial + + This class is dedicated to solve wave 2D equations: + + .. math:: + \p_t \hat f = \hat g - \gamma_f \hat f, + + \p_t \hat g = -\Omega^2 \hat f - \gamma_g \hat g, + + This purely linear wave equation can alternatively be written as + as :math:`\p_t X = M X`, with + + .. math:: + + X = \begin{pmatrix} \hat f \\ \hat g \end{pmatrix} + \ \ \text{and}\ \ + M = \begin{pmatrix} -\gamma_f & 1 \\ + -\Omega^2 & -\gamma_g \end{pmatrix}, + + where the three coefficients usually depend on the wavenumber. + The eigenvalues are :math:`\sigma_\pm = - \bar \gamma \pm i \tilde + \Omega`, where :math:`\bar \gamma = (\gamma_f + \gamma_g)/2` and + + .. math:: + + \tilde \Omega = \Omega \sqrt{1 + + \frac{1}{\Omega^2}(\gamma_f\gamma_g - \bar \gamma^2)}. + + The (not normalized) eigenvectors can be expressed as + + .. math:: + + V_\pm = \begin{pmatrix} 1 \\ \sigma_\pm + \gamma_f \end{pmatrix}. + + The state can be represented by a vector :math:`A` that verifies + :math:`X = V A`, where :math:`V` is the base matrix + + .. math:: + + V = \begin{pmatrix} 1 & 1 \\ + \sigma_+ + \gamma_f & \sigma_- + \gamma_f \end{pmatrix}. + + The inverse base matrix is given by + + .. math:: + + V^{-1} = \frac{i}{2\tilde \Omega} + \begin{pmatrix} + \sigma_- + \gamma_f & -1 \\ + -\sigma_+ - \gamma_f & 1 \end{pmatrix}. + + + """ + + @staticmethod + def _complete_params_with_default(params): + """This static method is used to complete the *params* container. + """ + SimulBasePseudoSpectral._complete_params_with_default(params) + attribs = {'c2': 1., 'f': 0} + params.set_attribs(attribs) + + def __init__(self, params): + super(Simul, self).__init__(params, info_solver) + + def tendencies_nonlin(self, state_fft=None): + + tendencies_fft = SetOfVariables( + like_this_sov=self.state.state_fft, + name_type_variables='tendencies_nonlin') + + tendencies_fft.data[:] = 0. + + return tendencies_fft + + def compute_freq_complex(self, key): + if key == 'f_fft': + omega = self.oper.constant_arrayK(value=0) + elif key == 'g_fft': + omega = 1.j*np.sqrt(self.params.f**2 + + self.params.c2*self.oper.K2) + return omega + + +if __name__ == "__main__": + + import numpy as np + + import fluiddyn as fld + + params = fld.simul.create_params(info_solver) + + params.short_name_type_run = 'test' + + nh = 32 + Lh = 2*np.pi + params.oper.nx = nh + params.oper.ny = nh + params.oper.Lx = Lh + params.oper.Ly = Lh + + # params.oper.type_fft = 'FFTWPY' + + delta_x = params.oper.Lx/params.oper.nx + params.nu_8 = 2.*10e-1*params.forcing.forcing_rate**(1./3)*delta_x**8 + + params.time_stepping.t_end = 1. + + params.init_fields.type_flow_init = 'NOISE' + + params.FORCING = True + params.forcing.type = 'Random' + # 'Proportional' + # params.forcing.type_normalize + + # params.output.periods_print.print_stdout = 0.25 + + params.output.periods_save.phys_fields = 0.5 + params.output.periods_save.spectra = 0.5 + params.output.periods_save.spatial_means = 0.05 + params.output.periods_save.spect_energy_budg = 0.5 + params.output.periods_save.increments = 0.5 + + params.output.periods_plot.phys_fields = 0.0 + + params.output.ONLINE_PLOT_OK = True + + # params.output.spectra.HAS_TO_PLOT_SAVED = True + # params.output.spatial_means.HAS_TO_PLOT_SAVED = True + # params.output.spect_energy_budg.HAS_TO_PLOT_SAVED = True + # params.output.increments.HAS_TO_PLOT_SAVED = True + + params.output.phys_fields.field_to_plot = 'rot' + + sim = Simul(params) + + # sim.output.phys_fields.plot() + sim.time_stepping.start() + # sim.output.phys_fields.plot() + + fld.show() diff --git a/fluidsim/util/__init__.py b/fluidsim/util/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vdXRpbC9fX2luaXRfXy5weQ== --- /dev/null +++ b/fluidsim/util/__init__.py @@ -0,0 +1,7 @@ +"""Utilities for the numerical simulations (:mod:`fluidsim.util`) +======================================================================= + +""" + + + diff --git a/fluidsim/util/util.py b/fluidsim/util/util.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_Zmx1aWRzaW0vdXRpbC91dGlsLnB5 --- /dev/null +++ b/fluidsim/util/util.py @@ -0,0 +1,405 @@ +"""Utilities for the numerical simulations (:mod:`fluidsim.util`) +======================================================================= + +""" + +from __future__ import division, print_function + +import os as _os +import glob as _glob +import numpy as _np +from copy import deepcopy as _deepcopy + +import h5py as _h5py + +import operator as _operator +import numbers as _numbers + +from importlib import import_module + + +import fluiddyn as fld + +from fluiddyn.util import mpi + +from fluidsim import path_dir_results + +from fluidsim.base.params import ( + load_info_solver, load_params_simul, Parameters) + + +def module_solver_from_key(key=None): + """Return the string corresponding to a module solver.""" + key = key.lower() + keys = [ + 'ns2d', 'sw1l', 'burgers', 'plate2d', + 'sw1l.exactlin', 'sw1l.onlywaves', 'sw1l.modified', 'sw1l.etaj'] + + if key in keys: + part_path = key + else: + raise ValueError( + 'You have to give a proper solver key, name solver given: '+key) + + base_solvers = 'fluidsim.solvers' + module_solver = base_solvers+'.'+part_path+'.solver' + + return module_solver + + +def import_module_solver_from_key(key=None): + """Import and reload the solver. + + Parameters + ---------- + + key : str + The short name of a solver. Can be 'NS2D', 'SW1l' or 'MSW1l'. + + """ + return import_module(module_solver_from_key(key)) + + +def pathdir_from_namedir(name_dir=None): + """Return the path if a result directory.""" + if name_dir is None: + return _os.getcwd() + if name_dir[0] != '/' and name_dir[0] != '~': + name_dir = path_dir_results+'/'+name_dir + return _os.path.expanduser(name_dir) + + +class ModulesSolvers(dict): + """Dictionary to gather imported solvers.""" + def __init__(self, names_solvers): + for key in names_solvers: + self[key] = import_module_solver_from_key(key) + + +def name_file_from_time_approx(path_dir, t_approx=None): + """Return the file name whose time is the closest to the given time. + + """ + list_path_files = _glob.glob(path_dir+'/state_phys_t=*') + nb_files = len(list_path_files) + if nb_files == 0 and mpi.rank == 0: + raise ValueError('No state file in the dir\n'+path_dir) + times = _np.empty([nb_files]) + for ii in xrange(nb_files): + times[ii] = float(list_path_files[ii][-11:-4]) + if t_approx is None: + t_approx = times.max() + i_file = abs(times-t_approx).argmin() + name_file = list_path_files[i_file][-24:] + return name_file + + +def load_sim_for_plot(path_dir=None): + """Create a object Simul from a dir result.""" + path_dir = pathdir_from_namedir(path_dir) + solver = _import_solver_from_path(path_dir) + params = load_params_simul(path_dir=path_dir) + + params.path_run = path_dir + params.init_fields.type_flow_init = 'CONSTANT' + params.ONLY_COARSE_OPER = True + params.FORCING = False + params.NEW_DIR_RESULTS = False + params.SAVE = False + sim = solver.Simul(params) + return sim + + +def _import_solver_from_path(path_dir): + info_solver = load_info_solver(path_dir=path_dir) + solver = import_module(info_solver.module_name) + return solver + + +def load_state_phys_file(name_dir=None, t_approx=None): + """Create a simulation from a file.""" + + path_dir = pathdir_from_namedir(name_dir) + + solver = _import_solver_from_path(path_dir) + + # choose the file with the time closer to t_approx + name_file = name_file_from_time_approx(path_dir, t_approx) + path_file = _os.path.join(path_dir, name_file) + + with _h5py.File(path_file, 'r') as f: + params = Parameters(hdf5_object=f['info_simul']['params']) + + params.path_run = path_dir + params.NEW_DIR_RESULTS = False + params.SAVE = False + params.init_fields.type_flow_init = 'LOAD_FILE' + params.init_fields.path_file = path_file + sim = solver.Simul(params) + return sim + + +def modif_resolution_all_dir(t_approx=None, + coef_modif_resol=2, + dir_base=None): + """Save files with a modified resolution.""" + path_base = pathdir_from_namedir(dir_base) + list_dir_results = _glob.glob(path_base+'/SE2D*') + for path_dir in list_dir_results: + modif_resolution_from_dir(name_dir=path_dir, + t_approx=t_approx, + coef_modif_resol=coef_modif_resol, + PLOT=False) + + +def modif_resolution_from_dir(name_dir=None, + t_approx=None, + coef_modif_resol=2, + PLOT=True): + """Save a file with a modified resolution.""" + + path_dir = pathdir_from_namedir(name_dir) + + if mpi.nb_proc > 1: + raise ValueError('Do NOT use this script with MPI !\n' + 'The MPI version of get_state_from_obj_simul()\n' + 'is not implemented.') + + solver = _import_solver_from_path(path_dir) + + sim = load_state_phys_file(name_dir, t_approx) + + params2 = _deepcopy(sim.params) + params2.oper.nx = sim.params.oper.nx*coef_modif_resol + params2.oper.ny = sim.params.oper.ny*coef_modif_resol + params2.init_fields.type_flow_init = 'CONSTANT' + + sim2 = solver.Simul(params2) + sim2.init_fields.get_state_from_obj_simul(sim) + + print(sim2.params.path_run) + + sim2.output.path_run = path_dir+'/State_phys_{0}x{1}'.format( + sim2.params.oper.nx, sim2.params.oper.ny) + print('Save file in directory\n'+sim2.output.path_run) + sim2.output.phys_fields.save(particular_attr='modif_resolution') + + print('The new file is saved.') + + if PLOT: + sim.output.phys_fields.plot(numfig=0) + sim2.output.phys_fields.plot(numfig=1) + fld.show() + + +def times_start_end_from_path(path): + """Return the start and end times from a result directory path. + + """ + + path_file = path+'/stdout.txt' + if not _os.path.exists(path_file): + print('Given path does not exist:\n '+path) + return 666, 666 + + file_stdout = open(path_file, 'r') + + line = '' + while not line.startswith('it ='): + line = file_stdout.readline() + + words = line.split() + t_s = float(words[6]) + + # in order to get the informations at the end of the file, + # we do not want to read the full file... + file_stdout.seek(0, 2) # go to the end + nb_caract = file_stdout.tell() + nb_caract_to_read = min(nb_caract, 1000) + file_stdout.seek(-nb_caract_to_read, 2) + while line != '': + if line.startswith('it ='): + line_it = line + last_line = line + line = file_stdout.readline() + + if last_line.startswith('save state_phys'): + word = last_line.replace('=', ' ').split()[-1] + t_e = float(word.replace('.hd5', '')) + else: + words = line_it.split() + t_e = float(words[6]) + + # print('t_s = {0:.3f}, t_e = {1:.3f}'.format(t_s, t_e)) + + file_stdout.close() + + return t_s, t_e + + +class SetOfDirResults(object): + """Represent a set of result directories.""" + def __init__(self, arg): + if isinstance(arg, str): + dir_base = pathdir_from_namedir(arg) + paths_results = _glob.glob(dir_base+'/SE2D_*') + if len(paths_results) == 0: + print('No result directory in the directory\n'+dir_base) + else: + paths_results = arg + for ind, val in enumerate(arg): + paths_results[ind] = pathdir_from_namedir(val) + if len(paths_results) == 0: + print('paths_results empty') + + self.nb_dirs = len(paths_results) + + self.dico_paths = {} + self.dico_params = {} + + keys_values = ['c', 'f', 'name_solver', 'FORCING', 'nh'] + self.dico_values = {} + for k in keys_values: + self.dico_values[k] = [] + + for path_dir in paths_results: + path_file = path_dir+'/param_simul.h5' + + name_run = _os.path.split(path_dir)[1] + + if not _os.path.exists(path_file): + print('No file param_simul.h5 in dir\n'+path_dir+ + 'This directory is skipped...') + self.nb_dirs -= 1 + else: + self.dico_paths[name_run] = path_dir + + with _h5py.File(path_file, 'r') as f: + name_run2 = f.attrs['name_run'] + name_solver = f.attrs['name_solver'] + + if name_run != name_run2: + raise ValueError('name_run != name_run2') + + # old code that have to be modified... + params = Params(path_dir=path_dir, VERBOSE=False) + self.dico_params[name_run] = params + + params.add_a_param('name_solver', name_solver) + params.add_a_param('solver', name_solver) + params.add_a_param('name_run', name_run) + params.add_a_param('nh', params['nx']) + + if 'c2' in params.__dict__ and 'c' not in params.__dict__: + params.add_a_param('c', _np.sqrt(params['c2'])) + + for k in keys_values: + if not params[k] in self.dico_values[k]: + self.dico_values[k].append(params[k]) + + if self.nb_dirs > 1: + for k, v in self.dico_values.iteritems(): + v.sort() + if isinstance(v[0], _numbers.Number): + self.dico_values[k] = _np.array(v) + + self.paths = self.dico_paths.values() + + def dirs_from_values(self, k_sort='c2', **kwargs): + """Return a list of dirs from conditions. + + >>> paths = setofdir.dirs_from_values2( + >>> c2=100, f=('>', 1), nh=('=',1920)) + + """ + + kdirs_corresp = self.dico_params.keys() + for k, v in kwargs.iteritems(): + if isinstance(v, tuple): + str_operator = v[0] + value = v[1] + else: + str_operator = '==' + value = v + + if str_operator == '==': + cond = _operator.eq + elif str_operator == '!=': + cond = _operator.ne + elif str_operator == '<': + cond = _operator.lt + elif str_operator == '>': + cond = _operator.gt + elif str_operator == '>=': + cond = _operator.le + elif str_operator == '<=': + cond = _operator.ge + else: + raise ValueError( + 'Supports only the operators ==, !=, >, <, >=, <=') + + kdirs_corresp_temp = [kdir for kdir, params + in self.dico_params.iteritems() + if cond(params[k], value) + ] + + kdirs_corresp = list( + set(kdirs_corresp).intersection(kdirs_corresp_temp)) + + if len(kdirs_corresp) == 0 and mpi.rank == 0: + print('No result directory corresponds to the criteria.') + + kdirs_corresp.sort(key=lambda key: self.dico_params[key][k_sort]) + + return kdirs_corresp + + + def filter_old(self, solver=None, c2=None, f=None, + FORCING=None, nh=None): + """Return a filtered SetOfDirResults.""" + dirs = self.dirs_from_values(solver=solver, c2=c2, f=f, + FORCING=FORCING, nh=nh) + paths = [self.dico_paths[dir_i] for dir_i in dirs] + return SetOfDirResults(paths) + + + def filter(self, **kwargs): + """Return a filtered SetOfDirResults from conditions. + + >>> setofdir2 = setofdir.filter(c2=100, f=('>', 1), nh=('=',1920)) + """ + dirs = self.dirs_from_values(**kwargs) + paths = [self.dico_paths[dir_i] for dir_i in dirs] + return SetOfDirResults(paths) + + def path_larger_t_start(self): + """Return the path corresponding to the run with larger *t_start*. + + """ + if len(self.paths) == 1: + path = self.paths[0] + else: + t_s = -1. + for path_temp in self.paths: + t_s_temp, t_e = times_start_end_from_path(path_temp) + if t_s_temp > t_s: + path = path_temp + t_s = t_s_temp + return path + + def one_path_from_values(self, **kwargs): + """Return one path from parameter values. + + If there are two corresponding runs, a warning is written and + the function returns None. + """ + keys_corresp = self.dirs_from_values(**kwargs) + if len(keys_corresp) == 1: + return self.dico_paths[keys_corresp[0]] + elif len(keys_corresp) == 0: + print('No directory corresponds to the given values.') + elif len(keys_corresp) > 1: + print('More than one directory corresponds to the given value(s).') + paths = [self.dico_paths[dir_i] for dir_i in keys_corresp] + sod = SetOfDirResults(paths) + return sod.path_larger_t_start() diff --git a/include/mpi-compat.h b/include/mpi-compat.h new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_aW5jbHVkZS9tcGktY29tcGF0Lmg= --- /dev/null +++ b/include/mpi-compat.h @@ -0,0 +1,14 @@ +/* Author: Lisandro Dalcin */ +/* Contact: dalcinl@gmail.com */ + +#ifndef MPI_COMPAT_H +#define MPI_COMPAT_H + +#include <mpi.h> + +#if (MPI_VERSION < 3) && !defined(PyMPI_HAVE_MPI_Message) +typedef void *PyMPI_MPI_Message; +#define MPI_Message PyMPI_MPI_Message +#endif + +#endif/*MPI_COMPAT_H*/ diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_cHlsaW50cmM= --- /dev/null +++ b/pylintrc @@ -0,0 +1,361 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# DEPRECATED +# include-ids=no + +# DEPRECATED +# symbols=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence=HIGH, INFERENCE, INFERENCE_FAILURE + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +#disable= + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=colorized + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +# msg-template='{msg_id}:{line:3d},{column}: {obj}: {msg}' +msg-template='{C}:{line:3d},{column:2d}: {msg} ({symbol})' + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis +ignored-modules= + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input,file + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_,f,ax + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{1,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{1,30}$ + +# Regular expression matching correct constant names +const-rgx=[a-z_][a-z0-9_]{1,30}$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )?<?https?://\S+>?$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: en (aspell), en_CA +# (aspell), en_GB (aspell), en_US (aspell), fr (aspell), fr_CH (aspell), fr_FR +# (aspell), en_ZA (myspell), en_AU (myspell). +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_c2V0dXAuY2Zn --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +ignore = E225,E226,E303,E201,E202, W503 \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..49a6353d9e5ff1906540e01fed90d5e523cbcecf_c2V0dXAucHk= --- /dev/null +++ b/setup.py @@ -0,0 +1,187 @@ + +from setuptools import setup, find_packages + +try: + from Cython.Distutils.extension import Extension + from Cython.Distutils import build_ext +except ImportError: + from setuptools import Extension, build_ext + from distutils.command import build_ext + +import subprocess +import numpy as np + +import os +here = os.path.abspath(os.path.dirname(__file__)) + +import sys +if sys.version_info[:2] < (2, 7) or (3, 0) <= sys.version_info[0:2] < (3, 2): + raise RuntimeError("Python version 2.7 or >= 3.2 required.") + +# Get the long description from the relevant file +with open(os.path.join(here, 'README.rst')) as f: + long_description = f.read() +lines = long_description.splitlines(True) +long_description = ''.join(lines[8:]) + +# Get the version from the relevant file +execfile('fluidsim/_version.py') +# Get the development status from the version string +if 'a' in __version__: + devstatus = 'Development Status :: 3 - Alpha' +elif 'b' in __version__: + devstatus = 'Development Status :: 4 - Beta' +else: + devstatus = 'Development Status :: 5 - Production/Stable' + +ext_modules = [] + + +try: + import mpi4py +except ImportError: + MPI4PY = False + include_dirs_mpi = [] +else: + MPI4PY = True + os.environ["CC"] = 'mpicc' + include_dirs_mpi = [ + mpi4py.get_include(), + here+'/include'] + + +if MPI4PY: + path_sources = 'fluidsim/operators/fft/Sources_fftw2dmpiccy' + include_dirs = [path_sources, np.get_include()] + include_dirs_mpi + ext_fftw2dmpiccy = Extension( + 'fluidsim.operators.fft.fftw2dmpiccy', + include_dirs=include_dirs, + libraries=['mpi', 'fftw3', 'm'], + library_dirs=[], + sources=[path_sources+'/libcfftw2dmpi.c', + path_sources+'/fftw2dmpiccy.pyx']) + ext_modules.append(ext_fftw2dmpiccy) + + +path_sources = 'fluidsim/operators/fft/Sources_fftw2dmpicy' +include_dirs = [path_sources, np.get_include()] +libraries = ['m'] +if MPI4PY: + include_dirs.extend(include_dirs_mpi) + libraries.append('mpi') + +library_dirs = [] +if sys.platform == 'win32': + if MPI4PY: + raise ValueError( + 'We have to work on this case with MPI4PY on Windows...') + fftw_dir = r'c:\Prog\fftw-3.3.4-dll64' + library_dirs.append(fftw_dir) + include_dirs.append(fftw_dir) + libraries.append('libfftw3-3') +else: + libraries.append('fftw3') + +try: + subprocess.check_call('ldconfig -p | grep libfftws3_mpi', shell=True) + FFTW3MPI = True +except subprocess.CalledProcessError: + FFTW3MPI = False + +if FFTW3MPI: + libraries.append('fftw3_mpi') + + ext_fftw2dmpicy = Extension( + 'fluidsim.operators.fft.fftw2dmpicy', + include_dirs=include_dirs, + libraries=libraries, + library_dirs=library_dirs, + cython_compile_time_env={'MPI4PY': MPI4PY}, + sources=[path_sources+'/fftw2dmpicy.pyx']) + ext_modules.append(ext_fftw2dmpicy) + +path_sources = 'fluidsim/operators/CySources' +include_dirs = [path_sources, np.get_include()] +libraries = ['m'] +if MPI4PY: + include_dirs.extend(include_dirs_mpi) + libraries.extend(['mpi']) +ext_operators = Extension( + 'fluidsim.operators.operators', + include_dirs=include_dirs, + libraries=libraries, + library_dirs=[], + cython_compile_time_env={'MPI4PY': MPI4PY}, + sources=[path_sources+'/operators_cy.pyx']) + + +path_sources = 'fluidsim/operators/CySources' +include_dirs = [path_sources, np.get_include()] +if MPI4PY: + include_dirs.extend(include_dirs_mpi) +ext_sov = Extension( + 'fluidsim.operators.setofvariables', + include_dirs=include_dirs, + libraries=libraries, + library_dirs=[], + cython_compile_time_env={'MPI4PY': MPI4PY}, + sources=[path_sources+'/setofvariables_cy.pyx']) + + +path_sources = 'fluidsim/base/time_stepping' +ext_cyfunc = Extension( + 'fluidsim.base.time_stepping.pseudo_spect_cy', + include_dirs=[ + path_sources, + np.get_include()], + libraries=['m'], + library_dirs=[], + sources=[path_sources+'/pseudo_spect_cy.pyx']) + +ext_modules.extend([ + ext_operators, + ext_sov, + ext_cyfunc]) + + +setup(name='fluidsim', + version=__version__, + description=('Framework for studying fluid dynamics with simulations.'), + long_description=long_description, + keywords='Fluid dynamics, research', + author='Pierre Augier', + author_email='pierre.augier@legi.cnrs.fr', + url='https://bitbucket.org/fluiddyn/fluidsim', + license='CeCILL', + classifiers=[ + # How mature is this project? Common values are + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + devstatus, + 'Intended Audience :: Science/Research', + 'Intended Audience :: Education', + 'Topic :: Scientific/Engineering', + 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', + # actually CeCILL License (GPL compatible license for French laws) + # + # Specify the Python versions you support here. In particular, + # ensure that you indicate whether you support Python 2, + # Python 3 or both. + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + # 'Programming Language :: Python :: 3', + # 'Programming Language :: Python :: 3.3', + # 'Programming Language :: Python :: 3.4', + 'Programming Language :: Cython', + 'Programming Language :: C', + ], + packages=find_packages(exclude=['doc', 'script']), + install_requires=['fluiddyn', 'h5py', 'pyfftw'], + extras_require=dict( + doc=['Sphinx>=1.1', 'numpydoc'], + parallel=['mpi4py']), + # scripts=['bin/fluiddyn-stop-pumps'], + cmdclass={"build_ext": build_ext}, + ext_modules=ext_modules)