commit c0413846626982f771329078021271d2bcbbdd83 Author: hoellinger Date: Fri Jan 10 17:03:16 2025 +0100 Initial commit: v0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..76f4a5c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,58 @@ +# Contributing to SelfiSys + +We welcome contributions to the SelfiSys repository! Please follow these guidelines when contributing. + +--- + +## Reporting Issues + +If you find a bug or have a suggestion, please open an issue in the [GitHub repository](https://github.com/hoellin/selfisys_public). Include as much detail as possible: +- Steps to reproduce the issue +- Expected vs. actual behaviour +- Relevant error messages or logs (if applicable) +- Suggestions for improvement (if applicable) + +If you are unsure whether your issue is a bug or have questions about the code, you are welcome to open an issue for discussion. + +## Submitting Contributions + +1. Fork the repository and create a new branch for your changes. +2. Ensure your contributions are well-documented and adhere to the highest coding standards. +3. Test your changes thoroughly before submitting: + - Ensure Jupyter notebooks run without errors from top to bottom. + - Validate new functionality or fixes using appropriate test cases. +4. Before submitting a pull request, synchronise your fork with the main repository to incorporate upstream changes. + - Add the main repository as a remote. + ```bash + git remote add upstream https://github.com/hoellin/selfisys_public.git + ``` + - Fetch the latest changes from the upstream repository. + ```bash + git fetch upstream + ``` + - Merge the changes into your local repository. + ```bash + git merge upstream/main + ``` +5. Open a pull request describing your changes to the main repository. + +## Style Guidelines + +Follow best practices for Python coding and Jupyter notebooks. + +### Python code + +Refer to the [PEP 8 Style Guide](https://pep8.org/) for Python coding standards. + +### Jupyter Notebooks + +- Use clear and concise Markdown cells to explain code and results. +- Avoid leaving unnecessary output (e.g., debugging print statements). +- Ensure notebooks are runnable from top to bottom. +- For tips on creating clean and effective Jupyter notebooks, see [Jupyter Notebook Best Practices](https://realpython.com/jupyter-notebook-best-practices/). + +--- + +## Questions? + +If you have any questions or need further clarification, feel free to [contact the authors](mailto:tristan.hoellinger@iap.fr). \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9cecc1d --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e85d351 --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# SelfiSys: Assess the Impact of Systematic Effects in Galaxy Surveys + +[![arXiv](https://img.shields.io/badge/astro--ph.CO-arxiv%3A2412.04443-B31B1B.svg?style=flat)](https://arxiv.org/abs/2412.04443) +[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/hoellin/selfisys_public/blob/main/LICENSE) +[![GitHub version](https://img.shields.io/github/tag/hoellin/selfisys_public.svg?label=version)](https://github.com/hoellin/selfisys_public) +[![GitHub last commit](https://img.shields.io/github/last-commit/hoellin/selfisys_public.svg)](https://github.com/hoellin/selfisys_public/commits/main) + + +**SelfiSys** is a Python package designed to address the issue of model misspecification in field-based, implicit likelihood cosmological inference. + +It leverages the inferred initial matter power spectrum, enabling a thorough diagnosis of systematic effects in large-scale spectroscopic galaxy surveys. + +## Key Features + +- **Custom hidden-box forward models** + +We provide a `HiddenBox` class to simulate realistic spectroscopic galaxy surveys. It accommodates fully non-linear gravitational evolution, and incorporates multiple systematic effects observed in real-world survey, e.g., misspecified galaxy bias, survey mask, selection functions, dust extinction, line interlopers, or inaccurate gravity solver. + +- **Diagnosis of systematic effects** + +Diagnose the impact of systematic effects using the inferred initial matter power spectrum, prior to performing cosmological inference. + +- **Cosmological inference** + +Perform inference of cosmological parameters using Approximate Bayesian Computation (ABC) with a Population Monte Carlo (PMC) sampler. + +--- + +## Documentation + +The documentation, including a detailed API reference, is available at [hoellin.github.io/selfisys_public](https://hoellin.github.io/selfisys_public/). + +For practical examples demonstrating how to use SelfiSys, visit the [SelfiSys Examples Repository](https://github.com/hoellin/selfisys_examples). + +## Contributors + +- **Tristan Hoellinger**, [tristan.hoellinger@iap.fr](mailto:tristan.hoellinger@iap.fr) + Principal developer and maintainer, Institut d’Astrophysique de Paris (IAP). + +For information on contributing, refer to [CONTRIBUTING.md](CONTRIBUTING.md). + +## References + +If you use the SelfiSys package in your research, please cite the following paper and feel free to [contact the authors](mailto:tristan.hoellinger@iap.fr) for feedback, collaboration opportunities, or other inquiries. + +**Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum** +*Hoellinger, T. and Leclercq, F., 2024* +[arXiv:2412.04443](https://arxiv.org/abs/2412.04443) [[astro-ph.CO]](https://arxiv.org/abs/2412.04443) [[ADS]](https://ui.adsabs.harvard.edu/abs/arXiv:2412.04443) [[pdf]](https://arxiv.org/pdf/2412.04443) + +BibTeX entry for citation: +```bibtex +@ARTICLE{hoellinger2024diagnosing, + author = {Hoellinger, Tristan and Leclercq, Florent}, + title = "{Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum}", + journal = {arXiv e-prints}, + keywords = {Astrophysics - Cosmology and Nongalactic Astrophysics, Astrophysics - Instrumentation and Methods for Astrophysics}, + year = 2024, + month = dec, + eid = {arXiv:2412.04443}, + pages = {arXiv:2412.04443}, + doi = {10.48550/arXiv.2412.04443}, +archivePrefix = {arXiv}, + eprint = {2412.04443}, +primaryClass = {astro-ph.CO}, + adsurl = {https://ui.adsabs.harvard.edu/abs/2024arXiv241204443H}, + adsnote = {Provided by the SAO/NASA Astrophysics Data System} +} +``` + +## Requirements + +The code is written in Python 3.10 and depends on the following packages: +- [`pySELFI`](https://pyselfi.readthedocs.io/en/latest/): Python implementation of the Simulator Expansion for Likelihood-Free Inference. +- [`Simbelmynë`](https://simbelmyne.readthedocs.io/en/latest/): A hierarchical probabilistic simulator for generating synthetic galaxy survey data. +- [`ELFI`](https://elfi.readthedocs.io/en/latest/): A statistical software package for likelihood-free inference, implementing in particular Approximate Bayesian Computation (ABC) with a Population Monte Carlo (PMC) sampler. + +A comprehensive list of dependencies, including version specifications to ensure reproducibility, will be provided in a yaml file, along with installation instructions, in a future release. + +--- + +## License + +This software is distributed under the GPLv3 Licence. Please review the [LICENSE](https://github.com/hoellin/selfisys_public/blob/main/LICENSE) file in the repository to understand the terms of use and ensure compliance. By downloading and using this software, you agree to the terms of the licence. \ No newline at end of file diff --git a/REFERENCES.md b/REFERENCES.md new file mode 100644 index 0000000..d211609 --- /dev/null +++ b/REFERENCES.md @@ -0,0 +1,44 @@ +# References + +If you use the SelfiSys package in your research, please cite the following paper and feel free to [contact the authors](mailto:tristan.hoellinger@iap.fr) for feedback, collaboration opportunities, or other inquiries. + +**Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum** +*Hoellinger, T. and Leclercq, F., 2024* +[arXiv:2412.04443](https://arxiv.org/abs/2412.04443) [[astro-ph.CO]](https://arxiv.org/abs/2412.04443) [[ADS]](https://ui.adsabs.harvard.edu/abs/arXiv:2412.04443) [[pdf]](https://arxiv.org/pdf/2412.04443) + +BibTeX entry for citation: +```bibtex +@ARTICLE{hoellinger2024diagnosing, + author = {Hoellinger, Tristan and Leclercq, Florent}, + title = "{Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum}", + journal = {arXiv e-prints}, + keywords = {Astrophysics - Cosmology and Nongalactic Astrophysics, Astrophysics - Instrumentation and Methods for Astrophysics}, + year = 2024, + month = dec, + eid = {arXiv:2412.04443}, + pages = {arXiv:2412.04443}, + doi = {10.48550/arXiv.2412.04443}, +archivePrefix = {arXiv}, + eprint = {2412.04443}, +primaryClass = {astro-ph.CO}, + adsurl = {https://ui.adsabs.harvard.edu/abs/2024arXiv241204443H}, + adsnote = {Provided by the SAO/NASA Astrophysics Data System} +} +``` + +Other references relevant to the SelfiSys pipeline and cited in the code are listed below. + +- **[jasche2010bayesian]** +Jasche, J., Kitaura, F. S., Wandelt, B. D., and Enßlin, T. A., “Bayesian power-spectrum inference for large-scale structure data”, Monthly Notices of the Royal Astronomical Society, vol. 406, no. 1, OUP, pp. 60–85, 2010. [doi:10.1111/j.1365-2966.2010.16610.x](https://doi.org/10.48550/arXiv.0911.2493). + +- **[gil2015power]** +Gil-Marín, H., “The power spectrum and bispectrum of SDSS DR11 BOSS galaxies - I. Bias and gravity”, Monthly Notices of the Royal Astronomical Society, vol. 451, no. 1, OUP, pp. 539–580, 2015. [doi:10.1093/mnras/stv961](https://doi.org/10.48550/arXiv.1407.5668). + +- **[howlett2015clustering]** +Howlett, C., Ross, A. J., Samushia, L., Percival, W. J., and Manera, M., “The clustering of the SDSS main galaxy sample - II. Mock galaxy catalogues and a measurement of the growth of structure from redshift space distortions at z = 0.15”, Monthly Notices of the Royal Astronomical Society, vol. 449, no. 1, OUP, pp. 848–866, 2015. [doi:10.1093/mnras/stu2693](https://doi.org/10.48550/arXiv.1409.3238). + +- **[leclercq2019primordial]** +Florent Leclercq, Wolfgang Enzi, Jens Jasche, Alan Heavens, Primordial power spectrum and cosmology from black-box galaxy surveys, Monthly Notices of the Royal Astronomical Society, Volume 490, Issue 3, December 2019, Pages 4237–4253, [doi:10.1093/mnras/stz2718](https://doi.org/10.1093/mnras/stz2718). + +- **[hoellinger2024diagnosing]** +Hoellinger, T. and Leclercq, F., “Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum”, arXiv e-prints, Art. no. arXiv:2412.04443, 2024. [doi.org:10.48550/arXiv.2412.04443](https://doi.org/10.48550/arXiv.2412.04443). \ No newline at end of file diff --git a/docs/CONTRIBUTING.html b/docs/CONTRIBUTING.html new file mode 100644 index 0000000..19271a6 --- /dev/null +++ b/docs/CONTRIBUTING.html @@ -0,0 +1,384 @@ + + + + + + + + + Contributing to SelfiSys — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/README.html b/docs/README.html new file mode 100644 index 0000000..28e80c9 --- /dev/null +++ b/docs/README.html @@ -0,0 +1,387 @@ + + + + + + + + + SelfiSys: Assess the Impact of Systematic Effects in Galaxy Surveys — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/REFERENCES.html b/docs/REFERENCES.html new file mode 100644 index 0000000..21c7a6c --- /dev/null +++ b/docs/REFERENCES.html @@ -0,0 +1,350 @@ + + + + + + + + + References — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/index.html b/docs/_modules/index.html new file mode 100644 index 0000000..858d661 --- /dev/null +++ b/docs/_modules/index.html @@ -0,0 +1,326 @@ + + + + + + + + Overview: module code — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/grf.html b/docs/_modules/selfisys/grf.html new file mode 100644 index 0000000..bdca704 --- /dev/null +++ b/docs/_modules/selfisys/grf.html @@ -0,0 +1,423 @@ + + + + + + + + selfisys.grf — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/hiddenbox.html b/docs/_modules/selfisys/hiddenbox.html new file mode 100644 index 0000000..5d63d1c --- /dev/null +++ b/docs/_modules/selfisys/hiddenbox.html @@ -0,0 +1,1848 @@ + + + + + + + + selfisys.hiddenbox — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/normalise_hb.html b/docs/_modules/selfisys/normalise_hb.html new file mode 100644 index 0000000..c5dac83 --- /dev/null +++ b/docs/_modules/selfisys/normalise_hb.html @@ -0,0 +1,596 @@ + + + + + + + + selfisys.normalise_hb — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/prior.html b/docs/_modules/selfisys/prior.html new file mode 100644 index 0000000..4cae76c --- /dev/null +++ b/docs/_modules/selfisys/prior.html @@ -0,0 +1,1028 @@ + + + + + + + + selfisys.prior — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/sbmy_interface.html b/docs/_modules/selfisys/sbmy_interface.html new file mode 100644 index 0000000..82bf1ec --- /dev/null +++ b/docs/_modules/selfisys/sbmy_interface.html @@ -0,0 +1,1212 @@ + + + + + + + + selfisys.sbmy_interface — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/selection_functions.html b/docs/_modules/selfisys/selection_functions.html new file mode 100644 index 0000000..48f0cc3 --- /dev/null +++ b/docs/_modules/selfisys/selection_functions.html @@ -0,0 +1,638 @@ + + + + + + + + selfisys.selection_functions — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/selfi_interface.html b/docs/_modules/selfisys/selfi_interface.html new file mode 100644 index 0000000..80b1609 --- /dev/null +++ b/docs/_modules/selfisys/selfi_interface.html @@ -0,0 +1,367 @@ + + + + + + + + selfisys.selfi_interface — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/setup_model.html b/docs/_modules/selfisys/setup_model.html new file mode 100644 index 0000000..2946ad5 --- /dev/null +++ b/docs/_modules/selfisys/setup_model.html @@ -0,0 +1,645 @@ + + + + + + + + selfisys.setup_model — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/utils/examples_utils.html b/docs/_modules/selfisys/utils/examples_utils.html new file mode 100644 index 0000000..34492ce --- /dev/null +++ b/docs/_modules/selfisys/utils/examples_utils.html @@ -0,0 +1,342 @@ + + + + + + + + selfisys.utils.examples_utils — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/utils/logger.html b/docs/_modules/selfisys/utils/logger.html new file mode 100644 index 0000000..da214e0 --- /dev/null +++ b/docs/_modules/selfisys/utils/logger.html @@ -0,0 +1,572 @@ + + + + + + + + selfisys.utils.logger — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/utils/low_level.html b/docs/_modules/selfisys/utils/low_level.html new file mode 100644 index 0000000..6651f24 --- /dev/null +++ b/docs/_modules/selfisys/utils/low_level.html @@ -0,0 +1,442 @@ + + + + + + + + selfisys.utils.low_level — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/utils/parser.html b/docs/_modules/selfisys/utils/parser.html new file mode 100644 index 0000000..eba19e8 --- /dev/null +++ b/docs/_modules/selfisys/utils/parser.html @@ -0,0 +1,481 @@ + + + + + + + + selfisys.utils.parser — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/utils/path_utils.html b/docs/_modules/selfisys/utils/path_utils.html new file mode 100644 index 0000000..85d318a --- /dev/null +++ b/docs/_modules/selfisys/utils/path_utils.html @@ -0,0 +1,525 @@ + + + + + + + + selfisys.utils.path_utils — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/utils/plot_examples.html b/docs/_modules/selfisys/utils/plot_examples.html new file mode 100644 index 0000000..9e5d2fc --- /dev/null +++ b/docs/_modules/selfisys/utils/plot_examples.html @@ -0,0 +1,942 @@ + + + + + + + + selfisys.utils.plot_examples — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/utils/plot_params.html b/docs/_modules/selfisys/utils/plot_params.html new file mode 100644 index 0000000..ffbabfc --- /dev/null +++ b/docs/_modules/selfisys/utils/plot_params.html @@ -0,0 +1,662 @@ + + + + + + + + selfisys.utils.plot_params — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/utils/plot_utils.html b/docs/_modules/selfisys/utils/plot_utils.html new file mode 100644 index 0000000..7dab39f --- /dev/null +++ b/docs/_modules/selfisys/utils/plot_utils.html @@ -0,0 +1,2741 @@ + + + + + + + + selfisys.utils.plot_utils — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/utils/timestepping.html b/docs/_modules/selfisys/utils/timestepping.html new file mode 100644 index 0000000..dfcf431 --- /dev/null +++ b/docs/_modules/selfisys/utils/timestepping.html @@ -0,0 +1,389 @@ + + + + + + + + selfisys.utils.timestepping — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/utils/tools.html b/docs/_modules/selfisys/utils/tools.html new file mode 100644 index 0000000..acc5ea6 --- /dev/null +++ b/docs/_modules/selfisys/utils/tools.html @@ -0,0 +1,644 @@ + + + + + + + + selfisys.utils.tools — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/selfisys/utils/workers.html b/docs/_modules/selfisys/utils/workers.html new file mode 100644 index 0000000..f8347bc --- /dev/null +++ b/docs/_modules/selfisys/utils/workers.html @@ -0,0 +1,702 @@ + + + + + + + + selfisys.utils.workers — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_sources/CONTRIBUTING.md.txt b/docs/_sources/CONTRIBUTING.md.txt new file mode 100644 index 0000000..76f4a5c --- /dev/null +++ b/docs/_sources/CONTRIBUTING.md.txt @@ -0,0 +1,58 @@ +# Contributing to SelfiSys + +We welcome contributions to the SelfiSys repository! Please follow these guidelines when contributing. + +--- + +## Reporting Issues + +If you find a bug or have a suggestion, please open an issue in the [GitHub repository](https://github.com/hoellin/selfisys_public). Include as much detail as possible: +- Steps to reproduce the issue +- Expected vs. actual behaviour +- Relevant error messages or logs (if applicable) +- Suggestions for improvement (if applicable) + +If you are unsure whether your issue is a bug or have questions about the code, you are welcome to open an issue for discussion. + +## Submitting Contributions + +1. Fork the repository and create a new branch for your changes. +2. Ensure your contributions are well-documented and adhere to the highest coding standards. +3. Test your changes thoroughly before submitting: + - Ensure Jupyter notebooks run without errors from top to bottom. + - Validate new functionality or fixes using appropriate test cases. +4. Before submitting a pull request, synchronise your fork with the main repository to incorporate upstream changes. + - Add the main repository as a remote. + ```bash + git remote add upstream https://github.com/hoellin/selfisys_public.git + ``` + - Fetch the latest changes from the upstream repository. + ```bash + git fetch upstream + ``` + - Merge the changes into your local repository. + ```bash + git merge upstream/main + ``` +5. Open a pull request describing your changes to the main repository. + +## Style Guidelines + +Follow best practices for Python coding and Jupyter notebooks. + +### Python code + +Refer to the [PEP 8 Style Guide](https://pep8.org/) for Python coding standards. + +### Jupyter Notebooks + +- Use clear and concise Markdown cells to explain code and results. +- Avoid leaving unnecessary output (e.g., debugging print statements). +- Ensure notebooks are runnable from top to bottom. +- For tips on creating clean and effective Jupyter notebooks, see [Jupyter Notebook Best Practices](https://realpython.com/jupyter-notebook-best-practices/). + +--- + +## Questions? + +If you have any questions or need further clarification, feel free to [contact the authors](mailto:tristan.hoellinger@iap.fr). \ No newline at end of file diff --git a/docs/_sources/README.md.txt b/docs/_sources/README.md.txt new file mode 100644 index 0000000..e85d351 --- /dev/null +++ b/docs/_sources/README.md.txt @@ -0,0 +1,83 @@ +# SelfiSys: Assess the Impact of Systematic Effects in Galaxy Surveys + +[![arXiv](https://img.shields.io/badge/astro--ph.CO-arxiv%3A2412.04443-B31B1B.svg?style=flat)](https://arxiv.org/abs/2412.04443) +[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/hoellin/selfisys_public/blob/main/LICENSE) +[![GitHub version](https://img.shields.io/github/tag/hoellin/selfisys_public.svg?label=version)](https://github.com/hoellin/selfisys_public) +[![GitHub last commit](https://img.shields.io/github/last-commit/hoellin/selfisys_public.svg)](https://github.com/hoellin/selfisys_public/commits/main) + + +**SelfiSys** is a Python package designed to address the issue of model misspecification in field-based, implicit likelihood cosmological inference. + +It leverages the inferred initial matter power spectrum, enabling a thorough diagnosis of systematic effects in large-scale spectroscopic galaxy surveys. + +## Key Features + +- **Custom hidden-box forward models** + +We provide a `HiddenBox` class to simulate realistic spectroscopic galaxy surveys. It accommodates fully non-linear gravitational evolution, and incorporates multiple systematic effects observed in real-world survey, e.g., misspecified galaxy bias, survey mask, selection functions, dust extinction, line interlopers, or inaccurate gravity solver. + +- **Diagnosis of systematic effects** + +Diagnose the impact of systematic effects using the inferred initial matter power spectrum, prior to performing cosmological inference. + +- **Cosmological inference** + +Perform inference of cosmological parameters using Approximate Bayesian Computation (ABC) with a Population Monte Carlo (PMC) sampler. + +--- + +## Documentation + +The documentation, including a detailed API reference, is available at [hoellin.github.io/selfisys_public](https://hoellin.github.io/selfisys_public/). + +For practical examples demonstrating how to use SelfiSys, visit the [SelfiSys Examples Repository](https://github.com/hoellin/selfisys_examples). + +## Contributors + +- **Tristan Hoellinger**, [tristan.hoellinger@iap.fr](mailto:tristan.hoellinger@iap.fr) + Principal developer and maintainer, Institut d’Astrophysique de Paris (IAP). + +For information on contributing, refer to [CONTRIBUTING.md](CONTRIBUTING.md). + +## References + +If you use the SelfiSys package in your research, please cite the following paper and feel free to [contact the authors](mailto:tristan.hoellinger@iap.fr) for feedback, collaboration opportunities, or other inquiries. + +**Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum** +*Hoellinger, T. and Leclercq, F., 2024* +[arXiv:2412.04443](https://arxiv.org/abs/2412.04443) [[astro-ph.CO]](https://arxiv.org/abs/2412.04443) [[ADS]](https://ui.adsabs.harvard.edu/abs/arXiv:2412.04443) [[pdf]](https://arxiv.org/pdf/2412.04443) + +BibTeX entry for citation: +```bibtex +@ARTICLE{hoellinger2024diagnosing, + author = {Hoellinger, Tristan and Leclercq, Florent}, + title = "{Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum}", + journal = {arXiv e-prints}, + keywords = {Astrophysics - Cosmology and Nongalactic Astrophysics, Astrophysics - Instrumentation and Methods for Astrophysics}, + year = 2024, + month = dec, + eid = {arXiv:2412.04443}, + pages = {arXiv:2412.04443}, + doi = {10.48550/arXiv.2412.04443}, +archivePrefix = {arXiv}, + eprint = {2412.04443}, +primaryClass = {astro-ph.CO}, + adsurl = {https://ui.adsabs.harvard.edu/abs/2024arXiv241204443H}, + adsnote = {Provided by the SAO/NASA Astrophysics Data System} +} +``` + +## Requirements + +The code is written in Python 3.10 and depends on the following packages: +- [`pySELFI`](https://pyselfi.readthedocs.io/en/latest/): Python implementation of the Simulator Expansion for Likelihood-Free Inference. +- [`Simbelmynë`](https://simbelmyne.readthedocs.io/en/latest/): A hierarchical probabilistic simulator for generating synthetic galaxy survey data. +- [`ELFI`](https://elfi.readthedocs.io/en/latest/): A statistical software package for likelihood-free inference, implementing in particular Approximate Bayesian Computation (ABC) with a Population Monte Carlo (PMC) sampler. + +A comprehensive list of dependencies, including version specifications to ensure reproducibility, will be provided in a yaml file, along with installation instructions, in a future release. + +--- + +## License + +This software is distributed under the GPLv3 Licence. Please review the [LICENSE](https://github.com/hoellin/selfisys_public/blob/main/LICENSE) file in the repository to understand the terms of use and ensure compliance. By downloading and using this software, you agree to the terms of the licence. \ No newline at end of file diff --git a/docs/_sources/REFERENCES.md.txt b/docs/_sources/REFERENCES.md.txt new file mode 100644 index 0000000..d211609 --- /dev/null +++ b/docs/_sources/REFERENCES.md.txt @@ -0,0 +1,44 @@ +# References + +If you use the SelfiSys package in your research, please cite the following paper and feel free to [contact the authors](mailto:tristan.hoellinger@iap.fr) for feedback, collaboration opportunities, or other inquiries. + +**Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum** +*Hoellinger, T. and Leclercq, F., 2024* +[arXiv:2412.04443](https://arxiv.org/abs/2412.04443) [[astro-ph.CO]](https://arxiv.org/abs/2412.04443) [[ADS]](https://ui.adsabs.harvard.edu/abs/arXiv:2412.04443) [[pdf]](https://arxiv.org/pdf/2412.04443) + +BibTeX entry for citation: +```bibtex +@ARTICLE{hoellinger2024diagnosing, + author = {Hoellinger, Tristan and Leclercq, Florent}, + title = "{Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum}", + journal = {arXiv e-prints}, + keywords = {Astrophysics - Cosmology and Nongalactic Astrophysics, Astrophysics - Instrumentation and Methods for Astrophysics}, + year = 2024, + month = dec, + eid = {arXiv:2412.04443}, + pages = {arXiv:2412.04443}, + doi = {10.48550/arXiv.2412.04443}, +archivePrefix = {arXiv}, + eprint = {2412.04443}, +primaryClass = {astro-ph.CO}, + adsurl = {https://ui.adsabs.harvard.edu/abs/2024arXiv241204443H}, + adsnote = {Provided by the SAO/NASA Astrophysics Data System} +} +``` + +Other references relevant to the SelfiSys pipeline and cited in the code are listed below. + +- **[jasche2010bayesian]** +Jasche, J., Kitaura, F. S., Wandelt, B. D., and Enßlin, T. A., “Bayesian power-spectrum inference for large-scale structure data”, Monthly Notices of the Royal Astronomical Society, vol. 406, no. 1, OUP, pp. 60–85, 2010. [doi:10.1111/j.1365-2966.2010.16610.x](https://doi.org/10.48550/arXiv.0911.2493). + +- **[gil2015power]** +Gil-Marín, H., “The power spectrum and bispectrum of SDSS DR11 BOSS galaxies - I. Bias and gravity”, Monthly Notices of the Royal Astronomical Society, vol. 451, no. 1, OUP, pp. 539–580, 2015. [doi:10.1093/mnras/stv961](https://doi.org/10.48550/arXiv.1407.5668). + +- **[howlett2015clustering]** +Howlett, C., Ross, A. J., Samushia, L., Percival, W. J., and Manera, M., “The clustering of the SDSS main galaxy sample - II. Mock galaxy catalogues and a measurement of the growth of structure from redshift space distortions at z = 0.15”, Monthly Notices of the Royal Astronomical Society, vol. 449, no. 1, OUP, pp. 848–866, 2015. [doi:10.1093/mnras/stu2693](https://doi.org/10.48550/arXiv.1409.3238). + +- **[leclercq2019primordial]** +Florent Leclercq, Wolfgang Enzi, Jens Jasche, Alan Heavens, Primordial power spectrum and cosmology from black-box galaxy surveys, Monthly Notices of the Royal Astronomical Society, Volume 490, Issue 3, December 2019, Pages 4237–4253, [doi:10.1093/mnras/stz2718](https://doi.org/10.1093/mnras/stz2718). + +- **[hoellinger2024diagnosing]** +Hoellinger, T. and Leclercq, F., “Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum”, arXiv e-prints, Art. no. arXiv:2412.04443, 2024. [doi.org:10.48550/arXiv.2412.04443](https://doi.org/10.48550/arXiv.2412.04443). \ No newline at end of file diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt new file mode 100644 index 0000000..26dda27 --- /dev/null +++ b/docs/_sources/index.rst.txt @@ -0,0 +1,95 @@ +SelfiSys: Assess the Impact of Systematic Effects in Galaxy Surveys +=================================================================== + +.. image:: https://img.shields.io/badge/astro--ph.CO-arxiv%3A2412.04443-B31B1B.svg + :target: https://arxiv.org/abs/2412.04443 + :alt: arXiv + +.. image:: https://img.shields.io/github/v/tag/hoellin/selfisys_public.svg?label=version + :target: https://github.com/hoellin/selfisys_public/releases + :alt: GitHub Release + +.. image:: https://img.shields.io/github/last-commit/hoellin/selfisys_public + :target: https://github.com/hoellin/selfisys_public/commits/main + :alt: Last Commit + +.. image:: https://img.shields.io/badge/License-GPLv3-blue.svg + :target: https://github.com/hoellin/selfisys_public/blob/main/LICENSE + :alt: License + +**SelfiSys** is a Python package designed to address the issue of model misspecification in field-based, implicit likelihood cosmological inference. + +It leverages the inferred initial matter power spectrum, enabling a thorough diagnosis of systematic effects in large-scale spectroscopic galaxy surveys. + +Key Features +------------ + +- **Custom hidden-box forward models** + We provide a `HiddenBox` class to simulate realistic spectroscopic galaxy surveys. It accommodates fully non-linear gravitational evolution, and incorporates multiple systematic effects observed in real-world survey, e.g., misspecified galaxy bias, survey mask, selection functions, dust extinction, line interlopers, or inaccurate gravity solver. +- **Diagnosis of systematic effects** + Diagnose the impact of systematic effects using the inferred initial matter power spectrum, prior to performing cosmological inference. +- **Cosmological inference** + Perform inference of cosmological parameters using Approximate Bayesian Computation (ABC) with a Population Monte Carlo (PMC) sampler. + +For practical examples demonstrating how to use SelfiSys, visit the `SelfiSys Examples Repository `_. + +References +---------- + +If you use the SelfiSys package in your research, please cite the following paper and feel free to `contact the authors `_ for feedback, collaboration opportunities, or other inquiries. + +**Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum** +*Hoellinger, T. and Leclercq, F., arXiv e-prints*, 2024 +`arXiv:2412.04443 `_ +`[astro-ph.CO] `_ +`[ADS] `_ +`[pdf] `_ + +Contributors +------------ + +- **Tristan Hoellinger** + `tristan.hoellinger@iap.fr `_ + + Principal developer and maintainer, Institut d’Astrophysique de Paris (IAP). + +License +------- + +This software is distributed under the GPLv3 Licence. Please review the `LICENSE `_ file in the repository to understand the terms of use and ensure compliance. By downloading and using this software, you agree to the terms of the licence. + +Requirements +------------ + +The code is written in Python 3.10 and depends on the following packages: + +- `pySELFI `_: Python implementation of the Simulator Expansion for Likelihood-Free Inference. +- `Simbelmynë `_: A hierarchical probabilistic simulator for generating synthetic galaxy survey data. +- `ELFI `_: A statistical software package for likelihood-free inference, implementing Approximate Bayesian Computation (ABC) with a Population Monte Carlo (PMC) sampler. + +A comprehensive list of dependencies, along with installation instructions, will be provided in a future release. + +.. toctree:: + :maxdepth: 2 + :caption: API Documentation + + selfisys.hiddenbox + selfisys.normalise_hb + selfisys.prior + selfisys.selection_functions + selfisys.selfi_interface + selfisys.sbmy_interface + selfisys.grf + selfisys.utils + +.. toctree:: + :maxdepth: 2 + :caption: Contribute + + ../../CONTRIBUTING.md + +.. toctree:: + :maxdepth: 2 + :caption: References + + ../../REFERENCES.md \ No newline at end of file diff --git a/docs/_sources/selfisys.global_parameters.rst.txt b/docs/_sources/selfisys.global_parameters.rst.txt new file mode 100644 index 0000000..0efb881 --- /dev/null +++ b/docs/_sources/selfisys.global_parameters.rst.txt @@ -0,0 +1,7 @@ +global_parameters +================= + +.. automodule:: selfisys.global_parameters + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/selfisys.grf.rst.txt b/docs/_sources/selfisys.grf.rst.txt new file mode 100644 index 0000000..d10cbf8 --- /dev/null +++ b/docs/_sources/selfisys.grf.rst.txt @@ -0,0 +1,7 @@ +grf +=== + +.. automodule:: selfisys.grf + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/selfisys.hiddenbox.rst.txt b/docs/_sources/selfisys.hiddenbox.rst.txt new file mode 100644 index 0000000..292952c --- /dev/null +++ b/docs/_sources/selfisys.hiddenbox.rst.txt @@ -0,0 +1,7 @@ +hiddenbox +========= + +.. automodule:: selfisys.hiddenbox + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/selfisys.normalise_hb.rst.txt b/docs/_sources/selfisys.normalise_hb.rst.txt new file mode 100644 index 0000000..108af97 --- /dev/null +++ b/docs/_sources/selfisys.normalise_hb.rst.txt @@ -0,0 +1,7 @@ +normalise_hb +============ + +.. automodule:: selfisys.normalise_hb + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/selfisys.prior.rst.txt b/docs/_sources/selfisys.prior.rst.txt new file mode 100644 index 0000000..30a73e2 --- /dev/null +++ b/docs/_sources/selfisys.prior.rst.txt @@ -0,0 +1,7 @@ +prior +===== + +.. automodule:: selfisys.prior + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/selfisys.rst.txt b/docs/_sources/selfisys.rst.txt new file mode 100644 index 0000000..1863c5a --- /dev/null +++ b/docs/_sources/selfisys.rst.txt @@ -0,0 +1,93 @@ +selfisys package +================ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + selfisys.utils + +Submodules +---------- + +selfisys.global\_parameters module +---------------------------------- + +.. automodule:: selfisys.global_parameters + :members: + :undoc-members: + :show-inheritance: + +selfisys.grf module +------------------- + +.. automodule:: selfisys.grf + :members: + :undoc-members: + :show-inheritance: + +selfisys.hiddenbox module +------------------------- + +.. automodule:: selfisys.hiddenbox + :members: + :undoc-members: + :show-inheritance: + +selfisys.normalise\_hb module +----------------------------- + +.. automodule:: selfisys.normalise_hb + :members: + :undoc-members: + :show-inheritance: + +selfisys.prior module +--------------------- + +.. automodule:: selfisys.prior + :members: + :undoc-members: + :show-inheritance: + +selfisys.sbmy\_interface module +------------------------------- + +.. automodule:: selfisys.sbmy_interface + :members: + :undoc-members: + :show-inheritance: + +selfisys.selection\_functions module +------------------------------------ + +.. automodule:: selfisys.selection_functions + :members: + :undoc-members: + :show-inheritance: + +selfisys.selfi\_interface module +-------------------------------- + +.. automodule:: selfisys.selfi_interface + :members: + :undoc-members: + :show-inheritance: + +selfisys.setup\_model module +---------------------------- + +.. automodule:: selfisys.setup_model + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: selfisys + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/selfisys.sbmy_interface.rst.txt b/docs/_sources/selfisys.sbmy_interface.rst.txt new file mode 100644 index 0000000..7f1f003 --- /dev/null +++ b/docs/_sources/selfisys.sbmy_interface.rst.txt @@ -0,0 +1,7 @@ +sbmy_interface +============== + +.. automodule:: selfisys.sbmy_interface + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/selfisys.selection_functions.rst.txt b/docs/_sources/selfisys.selection_functions.rst.txt new file mode 100644 index 0000000..fdb56b4 --- /dev/null +++ b/docs/_sources/selfisys.selection_functions.rst.txt @@ -0,0 +1,7 @@ +selection_functions +=================== + +.. automodule:: selfisys.selection_functions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/selfisys.selfi_interface.rst.txt b/docs/_sources/selfisys.selfi_interface.rst.txt new file mode 100644 index 0000000..32b1adf --- /dev/null +++ b/docs/_sources/selfisys.selfi_interface.rst.txt @@ -0,0 +1,7 @@ +selfi_interface +=============== + +.. automodule:: selfisys.selfi_interface + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/selfisys.setup_model.rst.txt b/docs/_sources/selfisys.setup_model.rst.txt new file mode 100644 index 0000000..a9036a8 --- /dev/null +++ b/docs/_sources/selfisys.setup_model.rst.txt @@ -0,0 +1,7 @@ +setup_model +=========== + +.. automodule:: selfisys.setup_model + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/selfisys.utils.rst.txt b/docs/_sources/selfisys.utils.rst.txt new file mode 100644 index 0000000..d5ac880 --- /dev/null +++ b/docs/_sources/selfisys.utils.rst.txt @@ -0,0 +1,101 @@ +selfisys.utils package +====================== + +Submodules +---------- + +selfisys.utils.examples\_utils module +------------------------------------- + +.. automodule:: selfisys.utils.examples_utils + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.logger module +---------------------------- + +.. automodule:: selfisys.utils.logger + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.low\_level module +-------------------------------- + +.. automodule:: selfisys.utils.low_level + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.parser module +---------------------------- + +.. automodule:: selfisys.utils.parser + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.path\_utils module +--------------------------------- + +.. automodule:: selfisys.utils.path_utils + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.plot\_examples module +------------------------------------ + +.. automodule:: selfisys.utils.plot_examples + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.plot\_params module +---------------------------------- + +.. automodule:: selfisys.utils.plot_params + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.plot\_utils module +--------------------------------- + +.. automodule:: selfisys.utils.plot_utils + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.timestepping module +---------------------------------- + +.. automodule:: selfisys.utils.timestepping + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.tools module +--------------------------- + +.. automodule:: selfisys.utils.tools + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.workers module +----------------------------- + +.. automodule:: selfisys.utils.workers + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: selfisys.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_static/_sphinx_javascript_frameworks_compat.js b/docs/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 0000000..8141580 --- /dev/null +++ b/docs/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/docs/_static/basic.css b/docs/_static/basic.css new file mode 100644 index 0000000..7ebbd6d --- /dev/null +++ b/docs/_static/basic.css @@ -0,0 +1,914 @@ +/* + * Sphinx stylesheet -- basic theme. + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin-top: 10px; +} + +ul.search li { + padding: 5px 0; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/_static/css/badge_only.css b/docs/_static/css/badge_only.css new file mode 100644 index 0000000..88ba55b --- /dev/null +++ b/docs/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} \ No newline at end of file diff --git a/docs/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/docs/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/docs/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/docs/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/docs/_static/css/fonts/fontawesome-webfont.eot b/docs/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/docs/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/docs/_static/css/fonts/fontawesome-webfont.svg b/docs/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/docs/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_static/css/fonts/fontawesome-webfont.ttf b/docs/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/docs/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/docs/_static/css/fonts/fontawesome-webfont.woff b/docs/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/docs/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/docs/_static/css/fonts/fontawesome-webfont.woff2 b/docs/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/docs/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/_static/css/fonts/lato-bold-italic.woff b/docs/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/docs/_static/css/fonts/lato-bold-italic.woff differ diff --git a/docs/_static/css/fonts/lato-bold-italic.woff2 b/docs/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/docs/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/docs/_static/css/fonts/lato-bold.woff b/docs/_static/css/fonts/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/docs/_static/css/fonts/lato-bold.woff differ diff --git a/docs/_static/css/fonts/lato-bold.woff2 b/docs/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/docs/_static/css/fonts/lato-bold.woff2 differ diff --git a/docs/_static/css/fonts/lato-normal-italic.woff b/docs/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/docs/_static/css/fonts/lato-normal-italic.woff differ diff --git a/docs/_static/css/fonts/lato-normal-italic.woff2 b/docs/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/docs/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/docs/_static/css/fonts/lato-normal.woff b/docs/_static/css/fonts/lato-normal.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/docs/_static/css/fonts/lato-normal.woff differ diff --git a/docs/_static/css/fonts/lato-normal.woff2 b/docs/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/docs/_static/css/fonts/lato-normal.woff2 differ diff --git a/docs/_static/css/theme.css b/docs/_static/css/theme.css new file mode 100644 index 0000000..0f14f10 --- /dev/null +++ b/docs/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/_static/doctools.js b/docs/_static/doctools.js new file mode 100644 index 0000000..0398ebb --- /dev/null +++ b/docs/_static/doctools.js @@ -0,0 +1,149 @@ +/* + * Base JavaScript utilities for all Sphinx HTML documentation. + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js new file mode 100644 index 0000000..c824aa9 --- /dev/null +++ b/docs/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '/Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/docs/_static/file.png b/docs/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/docs/_static/file.png differ diff --git a/docs/_static/fonts/Lato/lato-bold.eot b/docs/_static/fonts/Lato/lato-bold.eot new file mode 100644 index 0000000..3361183 Binary files /dev/null and b/docs/_static/fonts/Lato/lato-bold.eot differ diff --git a/docs/_static/fonts/Lato/lato-bold.ttf b/docs/_static/fonts/Lato/lato-bold.ttf new file mode 100644 index 0000000..29f691d Binary files /dev/null and b/docs/_static/fonts/Lato/lato-bold.ttf differ diff --git a/docs/_static/fonts/Lato/lato-bold.woff b/docs/_static/fonts/Lato/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/docs/_static/fonts/Lato/lato-bold.woff differ diff --git a/docs/_static/fonts/Lato/lato-bold.woff2 b/docs/_static/fonts/Lato/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/docs/_static/fonts/Lato/lato-bold.woff2 differ diff --git a/docs/_static/fonts/Lato/lato-bolditalic.eot b/docs/_static/fonts/Lato/lato-bolditalic.eot new file mode 100644 index 0000000..3d41549 Binary files /dev/null and b/docs/_static/fonts/Lato/lato-bolditalic.eot differ diff --git a/docs/_static/fonts/Lato/lato-bolditalic.ttf b/docs/_static/fonts/Lato/lato-bolditalic.ttf new file mode 100644 index 0000000..f402040 Binary files /dev/null and b/docs/_static/fonts/Lato/lato-bolditalic.ttf differ diff --git a/docs/_static/fonts/Lato/lato-bolditalic.woff b/docs/_static/fonts/Lato/lato-bolditalic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/docs/_static/fonts/Lato/lato-bolditalic.woff differ diff --git a/docs/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/_static/fonts/Lato/lato-bolditalic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/docs/_static/fonts/Lato/lato-bolditalic.woff2 differ diff --git a/docs/_static/fonts/Lato/lato-italic.eot b/docs/_static/fonts/Lato/lato-italic.eot new file mode 100644 index 0000000..3f82642 Binary files /dev/null and b/docs/_static/fonts/Lato/lato-italic.eot differ diff --git a/docs/_static/fonts/Lato/lato-italic.ttf b/docs/_static/fonts/Lato/lato-italic.ttf new file mode 100644 index 0000000..b4bfc9b Binary files /dev/null and b/docs/_static/fonts/Lato/lato-italic.ttf differ diff --git a/docs/_static/fonts/Lato/lato-italic.woff b/docs/_static/fonts/Lato/lato-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/docs/_static/fonts/Lato/lato-italic.woff differ diff --git a/docs/_static/fonts/Lato/lato-italic.woff2 b/docs/_static/fonts/Lato/lato-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/docs/_static/fonts/Lato/lato-italic.woff2 differ diff --git a/docs/_static/fonts/Lato/lato-regular.eot b/docs/_static/fonts/Lato/lato-regular.eot new file mode 100644 index 0000000..11e3f2a Binary files /dev/null and b/docs/_static/fonts/Lato/lato-regular.eot differ diff --git a/docs/_static/fonts/Lato/lato-regular.ttf b/docs/_static/fonts/Lato/lato-regular.ttf new file mode 100644 index 0000000..74decd9 Binary files /dev/null and b/docs/_static/fonts/Lato/lato-regular.ttf differ diff --git a/docs/_static/fonts/Lato/lato-regular.woff b/docs/_static/fonts/Lato/lato-regular.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/docs/_static/fonts/Lato/lato-regular.woff differ diff --git a/docs/_static/fonts/Lato/lato-regular.woff2 b/docs/_static/fonts/Lato/lato-regular.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/docs/_static/fonts/Lato/lato-regular.woff2 differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot new file mode 100644 index 0000000..79dc8ef Binary files /dev/null and b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf new file mode 100644 index 0000000..df5d1df Binary files /dev/null and b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot new file mode 100644 index 0000000..2f7ca78 Binary files /dev/null and b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf new file mode 100644 index 0000000..eb52a79 Binary files /dev/null and b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ diff --git a/docs/_static/jquery.js b/docs/_static/jquery.js new file mode 100644 index 0000000..c4c6022 --- /dev/null +++ b/docs/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t a.language.name.localeCompare(b.language.name)); + + const languagesHTML = ` +
+
Languages
+ ${languages + .map( + (translation) => ` +
+ ${translation.language.code} +
+ `, + ) + .join("\n")} +
+ `; + return languagesHTML; + } + + function renderVersions(config) { + if (!config.versions.active.length) { + return ""; + } + const versionsHTML = ` +
+
Versions
+ ${config.versions.active + .map( + (version) => ` +
+ ${version.slug} +
+ `, + ) + .join("\n")} +
+ `; + return versionsHTML; + } + + function renderDownloads(config) { + if (!Object.keys(config.versions.current.downloads).length) { + return ""; + } + const downloadsNameDisplay = { + pdf: "PDF", + epub: "Epub", + htmlzip: "HTML", + }; + + const downloadsHTML = ` +
+
Downloads
+ ${Object.entries(config.versions.current.downloads) + .map( + ([name, url]) => ` +
+ ${downloadsNameDisplay[name]} +
+ `, + ) + .join("\n")} +
+ `; + return downloadsHTML; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const flyout = ` +
+ + Read the Docs + v: ${config.versions.current.slug} + + +
+
+ ${renderLanguages(config)} + ${renderVersions(config)} + ${renderDownloads(config)} +
+
On Read the Docs
+
+ Project Home +
+
+ Builds +
+
+ Downloads +
+
+
+
Search
+
+
+ +
+
+
+
+ + Hosted by Read the Docs + +
+
+ `; + + // Inject the generated flyout into the body HTML element. + document.body.insertAdjacentHTML("beforeend", flyout); + + // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. + document + .querySelector("#flyout-search-form") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); + }) +} + +if (themeLanguageSelector || themeVersionSelector) { + function onSelectorSwitch(event) { + const option = event.target.selectedIndex; + const item = event.target.options[option]; + window.location.href = item.dataset.url; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const versionSwitch = document.querySelector( + "div.switch-menus > div.version-switch", + ); + if (themeVersionSelector) { + let versions = config.versions.active; + if (config.versions.current.hidden || config.versions.current.type === "external") { + versions.unshift(config.versions.current); + } + const versionSelect = ` + + `; + + versionSwitch.innerHTML = versionSelect; + versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + + const languageSwitch = document.querySelector( + "div.switch-menus > div.language-switch", + ); + + if (themeLanguageSelector) { + if (config.projects.translations.length) { + // Add the current language to the options on the selector + let languages = config.projects.translations.concat( + config.projects.current, + ); + languages = languages.sort((a, b) => + a.language.name.localeCompare(b.language.name), + ); + + const languageSelect = ` + + `; + + languageSwitch.innerHTML = languageSelect; + languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + else { + languageSwitch.remove(); + } + } + }); +} + +document.addEventListener("readthedocs-addons-data-ready", function (event) { + // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. + document + .querySelector("[role='search'] input") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); +}); \ No newline at end of file diff --git a/docs/_static/language_data.js b/docs/_static/language_data.js new file mode 100644 index 0000000..c7fe6c6 --- /dev/null +++ b/docs/_static/language_data.js @@ -0,0 +1,192 @@ +/* + * This script contains the language-specific data used by searchtools.js, + * namely the list of stopwords, stemmer, scorer and splitter. + */ + +var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; + + +/* Non-minified version is copied as a separate JS file, if available */ + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/docs/_static/minus.png b/docs/_static/minus.png new file mode 100644 index 0000000..d96755f Binary files /dev/null and b/docs/_static/minus.png differ diff --git a/docs/_static/plus.png b/docs/_static/plus.png new file mode 100644 index 0000000..7107cec Binary files /dev/null and b/docs/_static/plus.png differ diff --git a/docs/_static/pygments.css b/docs/_static/pygments.css new file mode 100644 index 0000000..84ab303 --- /dev/null +++ b/docs/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #9C6500 } /* Comment.Preproc */ +.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #E40000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #008400 } /* Generic.Inserted */ +.highlight .go { color: #717171 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #687822 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #767600 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #666666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #0000FF } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/_static/searchtools.js b/docs/_static/searchtools.js new file mode 100644 index 0000000..2c774d1 --- /dev/null +++ b/docs/_static/searchtools.js @@ -0,0 +1,632 @@ +/* + * Sphinx JavaScript utilities for the full-text search. + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename, kind] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename, kind] = item; + + let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, + ).replace('${resultCount}', resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; + + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + _parseQuery: (query) => { + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. + const normalResults = []; + const nonMainIndexResults = []; + + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase().trim(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score + boost, + filenames[file], + SearchResultKind.title, + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + SearchResultKind.index, + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } + } + } + } + + // lookup as object + objectTerms.forEach((term) => + normalResults.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + SearchResultKind.object, + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + SearchResultKind.text, + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/docs/_static/sphinx_highlight.js b/docs/_static/sphinx_highlight.js new file mode 100644 index 0000000..8a96c69 --- /dev/null +++ b/docs/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/docs/genindex.html b/docs/genindex.html new file mode 100644 index 0000000..092946a --- /dev/null +++ b/docs/genindex.html @@ -0,0 +1,955 @@ + + + + + + + + Index — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..971c808 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,458 @@ + + + + + + + + + SelfiSys: Assess the Impact of Systematic Effects in Galaxy Surveys — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/objects.inv b/docs/objects.inv new file mode 100644 index 0000000..ba915ad Binary files /dev/null and b/docs/objects.inv differ diff --git a/docs/py-modindex.html b/docs/py-modindex.html new file mode 100644 index 0000000..8ac5aa3 --- /dev/null +++ b/docs/py-modindex.html @@ -0,0 +1,432 @@ + + + + + + + + Python Module Index — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/search.html b/docs/search.html new file mode 100644 index 0000000..440a986 --- /dev/null +++ b/docs/search.html @@ -0,0 +1,327 @@ + + + + + + + + Search — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/docs/searchindex.js b/docs/searchindex.js new file mode 100644 index 0000000..2501d75 --- /dev/null +++ b/docs/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"API Documentation": [[3, null]], "Contribute": [[3, null]], "Contributing to SelfiSys": [[0, null]], "Contributors": [[1, "contributors"], [3, "contributors"]], "Documentation": [[1, "documentation"]], "Jupyter Notebooks": [[0, "jupyter-notebooks"]], "Key Features": [[1, "key-features"], [3, "key-features"]], "License": [[1, "license"], [3, "license"]], "Module contents": [[4, "module-selfisys"], [14, "module-selfisys.utils"]], "Python code": [[0, "python-code"]], "Questions?": [[0, "questions"]], "References": [[1, "references"], [2, null], [3, "references"], [3, null]], "Reporting Issues": [[0, "reporting-issues"]], "Requirements": [[1, "requirements"], [3, "requirements"]], "SelfiSys: Assess the Impact of Systematic Effects in Galaxy Surveys": [[1, null], [3, null]], "Style Guidelines": [[0, "style-guidelines"]], "Submitting Contributions": [[0, "submitting-contributions"]], "Submodules": [[4, "submodules"], [14, "submodules"]], "Subpackages": [[4, "subpackages"]], "global_parameters": [[5, null]], "grf": [[6, null]], "hiddenbox": [[7, null]], "normalise_hb": [[8, null]], "prior": [[9, null]], "sbmy_interface": [[10, null]], "selection_functions": [[11, null]], "selfi_interface": [[12, null]], "selfisys package": [[4, null]], "selfisys.global_parameters module": [[4, "module-selfisys.global_parameters"]], "selfisys.grf module": [[4, "module-selfisys.grf"]], "selfisys.hiddenbox module": [[4, "module-selfisys.hiddenbox"]], "selfisys.normalise_hb module": [[4, "module-selfisys.normalise_hb"]], "selfisys.prior module": [[4, "module-selfisys.prior"]], "selfisys.sbmy_interface module": [[4, "module-selfisys.sbmy_interface"]], "selfisys.selection_functions module": [[4, "module-selfisys.selection_functions"]], "selfisys.selfi_interface module": [[4, "module-selfisys.selfi_interface"]], "selfisys.setup_model module": [[4, "module-selfisys.setup_model"]], "selfisys.utils package": [[14, null]], "selfisys.utils.examples_utils module": [[14, "module-selfisys.utils.examples_utils"]], "selfisys.utils.logger module": [[14, "module-selfisys.utils.logger"]], "selfisys.utils.low_level module": [[14, "module-selfisys.utils.low_level"]], "selfisys.utils.parser module": [[14, "module-selfisys.utils.parser"]], "selfisys.utils.path_utils module": [[14, "module-selfisys.utils.path_utils"]], "selfisys.utils.plot_examples module": [[14, "module-selfisys.utils.plot_examples"]], "selfisys.utils.plot_params module": [[14, "module-selfisys.utils.plot_params"]], "selfisys.utils.plot_utils module": [[14, "module-selfisys.utils.plot_utils"]], "selfisys.utils.timestepping module": [[14, "module-selfisys.utils.timestepping"]], "selfisys.utils.tools module": [[14, "module-selfisys.utils.tools"]], "selfisys.utils.workers module": [[14, "module-selfisys.utils.workers"]], "setup_model": [[13, null]]}, "docnames": ["CONTRIBUTING", "README", "REFERENCES", "index", "selfisys", "selfisys.global_parameters", "selfisys.grf", "selfisys.hiddenbox", "selfisys.normalise_hb", "selfisys.prior", "selfisys.sbmy_interface", "selfisys.selection_functions", "selfisys.selfi_interface", "selfisys.setup_model", "selfisys.utils"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.todo": 2, "sphinx.ext.viewcode": 1}, "filenames": ["CONTRIBUTING.md", "README.md", "REFERENCES.md", "index.rst", "selfisys.rst", "selfisys.global_parameters.rst", "selfisys.grf.rst", "selfisys.hiddenbox.rst", "selfisys.normalise_hb.rst", "selfisys.prior.rst", "selfisys.sbmy_interface.rst", "selfisys.selection_functions.rst", "selfisys.selfi_interface.rst", "selfisys.setup_model.rst", "selfisys.utils.rst"], "indexentries": {"bool_sh() (in module selfisys.utils.parser)": [[14, "selfisys.utils.parser.bool_sh", false]], "check_files_exist() (in module selfisys.utils.parser)": [[14, "selfisys.utils.parser.check_files_exist", false]], "clear_large_plot() (in module selfisys.utils.examples_utils)": [[14, "selfisys.utils.examples_utils.clear_large_plot", false]], "compute() (selfisys.prior.planck_prior method)": [[4, "selfisys.prior.planck_prior.compute", false], [9, "selfisys.prior.planck_prior.compute", false]], "compute_alpha_cv() (in module selfisys.setup_model)": [[4, "selfisys.setup_model.compute_alpha_cv", false], [13, "selfisys.setup_model.compute_alpha_cv", false]], "compute_phi() (in module selfisys.sbmy_interface)": [[4, "selfisys.sbmy_interface.compute_Phi", false], [10, "selfisys.sbmy_interface.compute_Phi", false]], "compute_pool() (selfisys.hiddenbox.hiddenbox method)": [[4, "selfisys.hiddenbox.HiddenBox.compute_pool", false], [7, "selfisys.hiddenbox.HiddenBox.compute_pool", false]], "cosmo_vector_to_class_dict() (in module selfisys.utils.tools)": [[14, "selfisys.utils.tools.cosmo_vector_to_class_dict", false]], "cosmo_vector_to_simbelmyne_dict() (in module selfisys.utils.tools)": [[14, "selfisys.utils.tools.cosmo_vector_to_Simbelmyne_dict", false]], "covariance (selfisys.prior.planck_prior attribute)": [[4, "selfisys.prior.planck_prior.covariance", false], [9, "selfisys.prior.planck_prior.covariance", false]], "create_all_colormaps() (in module selfisys.utils.plot_params)": [[14, "selfisys.utils.plot_params.create_all_colormaps", false]], "create_colormap() (in module selfisys.utils.plot_params)": [[14, "selfisys.utils.plot_params.create_colormap", false]], "custom_stat() (in module selfisys.utils.tools)": [[14, "selfisys.utils.tools.custom_stat", false]], "customlogger (class in selfisys.utils.logger)": [[14, "selfisys.utils.logger.CustomLogger", false]], "customloggerhandler (class in selfisys.utils.logger)": [[14, "selfisys.utils.logger.CustomLoggerHandler", false]], "define_normalisation() (in module selfisys.normalise_hb)": [[4, "selfisys.normalise_hb.define_normalisation", false], [8, "selfisys.normalise_hb.define_normalisation", false]], "diagnostic() (selfisys.utils.logger.customlogger method)": [[14, "selfisys.utils.logger.CustomLogger.diagnostic", false]], "dynamic_text_scaling() (in module selfisys.utils.plot_params)": [[14, "selfisys.utils.plot_params.dynamic_text_scaling", false]], "emit() (selfisys.utils.logger.customloggerhandler method)": [[14, "selfisys.utils.logger.CustomLoggerHandler.emit", false]], "evaluate() (selfisys.hiddenbox.hiddenbox method)": [[4, "selfisys.hiddenbox.HiddenBox.evaluate", false], [7, "selfisys.hiddenbox.HiddenBox.evaluate", false]], "evaluate_gradient_of_symbelmyne() (in module selfisys.utils.workers)": [[14, "selfisys.utils.workers.evaluate_gradient_of_Symbelmyne", false]], "file_names_evaluate() (in module selfisys.utils.path_utils)": [[14, "selfisys.utils.path_utils.file_names_evaluate", false]], "fisher_rao() (in module selfisys.utils.tools)": [[14, "selfisys.utils.tools.fisher_rao", false]], "force_neglect_lightcone (selfisys.hiddenbox.hiddenbox property)": [[4, "selfisys.hiddenbox.HiddenBox.force_neglect_lightcone", false], [7, "selfisys.hiddenbox.HiddenBox.force_neglect_lightcone", false]], "force_recompute_mocks (selfisys.hiddenbox.hiddenbox property)": [[4, "selfisys.hiddenbox.HiddenBox.force_recompute_mocks", false], [7, "selfisys.hiddenbox.HiddenBox.force_recompute_mocks", false]], "g_sim_path (selfisys.setup_model.modelsetup attribute)": [[4, "selfisys.setup_model.ModelSetup.G_sim_path", false], [13, "selfisys.setup_model.ModelSetup.G_sim_path", false]], "g_ss_path (selfisys.setup_model.modelsetup attribute)": [[4, "selfisys.setup_model.ModelSetup.G_ss_path", false], [13, "selfisys.setup_model.ModelSetup.G_ss_path", false]], "generate_white_noise_field() (in module selfisys.sbmy_interface)": [[4, "selfisys.sbmy_interface.generate_white_noise_Field", false], [10, "selfisys.sbmy_interface.generate_white_noise_Field", false]], "get_contours() (in module selfisys.utils.plot_params)": [[14, "selfisys.utils.plot_params.get_contours", false]], "get_file_names() (in module selfisys.utils.path_utils)": [[14, "selfisys.utils.path_utils.get_file_names", false]], "get_k_max() (in module selfisys.utils.tools)": [[14, "selfisys.utils.tools.get_k_max", false]], "get_offset() (selfisys.utils.plot_params.scalarformatterforceformat_11 method)": [[14, "selfisys.utils.plot_params.ScalarFormatterForceFormat_11.get_offset", false]], "get_power_spectrum_from_cosmo() (in module selfisys.sbmy_interface)": [[4, "selfisys.sbmy_interface.get_power_spectrum_from_cosmo", false], [10, "selfisys.sbmy_interface.get_power_spectrum_from_cosmo", false]], "get_summary() (in module selfisys.prior)": [[4, "selfisys.prior.get_summary", false], [9, "selfisys.prior.get_summary", false]], "get_summary() (in module selfisys.utils.tools)": [[14, "selfisys.utils.tools.get_summary", false]], "getcustomlogger() (in module selfisys.utils.logger)": [[14, "selfisys.utils.logger.getCustomLogger", false]], "gravity_on (selfisys.hiddenbox.hiddenbox property)": [[4, "selfisys.hiddenbox.HiddenBox.gravity_on", false], [7, "selfisys.hiddenbox.HiddenBox.gravity_on", false]], "handle_time_stepping() (in module selfisys.sbmy_interface)": [[4, "selfisys.sbmy_interface.handle_time_stepping", false], [10, "selfisys.sbmy_interface.handle_time_stepping", false]], "hiddenbox (class in selfisys.hiddenbox)": [[4, "selfisys.hiddenbox.HiddenBox", false], [7, "selfisys.hiddenbox.HiddenBox", false]], "indent() (in module selfisys.selfi_interface)": [[4, "selfisys.selfi_interface.indent", false], [12, "selfisys.selfi_interface.indent", false]], "indent() (in module selfisys.utils.logger)": [[14, "selfisys.utils.logger.INDENT", false]], "init_selection() (selfisys.selection_functions.lognormalselection method)": [[4, "selfisys.selection_functions.LognormalSelection.init_selection", false], [11, "selfisys.selection_functions.LognormalSelection.init_selection", false]], "intnone() (in module selfisys.utils.parser)": [[14, "selfisys.utils.parser.intNone", false]], "inv_covariance (selfisys.prior.planck_prior attribute)": [[4, "selfisys.prior.planck_prior.inv_covariance", false], [9, "selfisys.prior.planck_prior.inv_covariance", false]], "joinstrs() (in module selfisys.utils.parser)": [[14, "selfisys.utils.parser.joinstrs", false]], "joinstrs_only() (in module selfisys.utils.parser)": [[14, "selfisys.utils.parser.joinstrs_only", false]], "k_s (selfisys.setup_model.modelsetup attribute)": [[4, "selfisys.setup_model.ModelSetup.k_s", false], [13, "selfisys.setup_model.ModelSetup.k_s", false]], "l (selfisys.setup_model.modelsetup attribute)": [[4, "selfisys.setup_model.ModelSetup.L", false], [13, "selfisys.setup_model.ModelSetup.L", false]], "load() (selfisys.prior.planck_prior class method)": [[4, "selfisys.prior.planck_prior.load", false], [9, "selfisys.prior.planck_prior.load", false]], "load_pool() (selfisys.hiddenbox.hiddenbox method)": [[4, "selfisys.hiddenbox.HiddenBox.load_pool", false], [7, "selfisys.hiddenbox.HiddenBox.load_pool", false]], "lognormals_z_to_x() (selfisys.selection_functions.lognormalselection method)": [[4, "selfisys.selection_functions.LognormalSelection.lognormals_z_to_x", false], [11, "selfisys.selection_functions.LognormalSelection.lognormals_z_to_x", false]], "lognormalselection (class in selfisys.selection_functions)": [[4, "selfisys.selection_functions.LognormalSelection", false], [11, "selfisys.selection_functions.LognormalSelection", false]], "logpdf() (selfisys.prior.planck_prior method)": [[4, "selfisys.prior.planck_prior.logpdf", false], [9, "selfisys.prior.planck_prior.logpdf", false]], "logposterior_hyperparameters_parallel() (in module selfisys.prior)": [[4, "selfisys.prior.logposterior_hyperparameters_parallel", false], [9, "selfisys.prior.logposterior_hyperparameters_parallel", false]], "make_data() (selfisys.hiddenbox.hiddenbox method)": [[4, "selfisys.hiddenbox.HiddenBox.make_data", false], [7, "selfisys.hiddenbox.HiddenBox.make_data", false]], "mean (selfisys.prior.planck_prior attribute)": [[4, "selfisys.prior.planck_prior.mean", false], [9, "selfisys.prior.planck_prior.mean", false]], "merge_nts() (in module selfisys.utils.timestepping)": [[14, "selfisys.utils.timestepping.merge_nTS", false]], "modelsetup (class in selfisys.setup_model)": [[4, "selfisys.setup_model.ModelSetup", false], [13, "selfisys.setup_model.ModelSetup", false]], "modified_selfi (selfisys.hiddenbox.hiddenbox property)": [[4, "selfisys.hiddenbox.HiddenBox.modified_selfi", false], [7, "selfisys.hiddenbox.HiddenBox.modified_selfi", false]], "module": [[4, "module-selfisys", false], [4, "module-selfisys.global_parameters", false], [4, "module-selfisys.grf", false], [4, "module-selfisys.hiddenbox", false], [4, "module-selfisys.normalise_hb", false], [4, "module-selfisys.prior", false], [4, "module-selfisys.sbmy_interface", false], [4, "module-selfisys.selection_functions", false], [4, "module-selfisys.selfi_interface", false], [4, "module-selfisys.setup_model", false], [5, "module-selfisys.global_parameters", false], [6, "module-selfisys.grf", false], [7, "module-selfisys.hiddenbox", false], [8, "module-selfisys.normalise_hb", false], [9, "module-selfisys.prior", false], [10, "module-selfisys.sbmy_interface", false], [11, "module-selfisys.selection_functions", false], [12, "module-selfisys.selfi_interface", false], [13, "module-selfisys.setup_model", false], [14, "module-selfisys.utils", false], [14, "module-selfisys.utils.examples_utils", false], [14, "module-selfisys.utils.logger", false], [14, "module-selfisys.utils.low_level", false], [14, "module-selfisys.utils.parser", false], [14, "module-selfisys.utils.path_utils", false], [14, "module-selfisys.utils.plot_examples", false], [14, "module-selfisys.utils.plot_params", false], [14, "module-selfisys.utils.plot_utils", false], [14, "module-selfisys.utils.timestepping", false], [14, "module-selfisys.utils.tools", false], [14, "module-selfisys.utils.workers", false]], "multiple_lognormal() (selfisys.selection_functions.lognormalselection method)": [[4, "selfisys.selection_functions.LognormalSelection.multiple_lognormal", false], [11, "selfisys.selection_functions.LognormalSelection.multiple_lognormal", false]], "multiple_lognormal_z() (selfisys.selection_functions.lognormalselection method)": [[4, "selfisys.selection_functions.LognormalSelection.multiple_lognormal_z", false], [11, "selfisys.selection_functions.LognormalSelection.multiple_lognormal_z", false]], "nbin_max (selfisys.prior.planck_prior property)": [[4, "selfisys.prior.planck_prior.Nbin_max", false], [9, "selfisys.prior.planck_prior.Nbin_max", false]], "nbin_min (selfisys.prior.planck_prior property)": [[4, "selfisys.prior.planck_prior.Nbin_min", false], [9, "selfisys.prior.planck_prior.Nbin_min", false]], "none_or_bool_or_str() (in module selfisys.utils.parser)": [[14, "selfisys.utils.parser.none_or_bool_or_str", false]], "none_or_bool_or_str() (in module selfisys.utils.tools)": [[14, "selfisys.utils.tools.none_or_bool_or_str", false]], "npop (selfisys.hiddenbox.hiddenbox property)": [[4, "selfisys.hiddenbox.HiddenBox.Npop", false], [7, "selfisys.hiddenbox.HiddenBox.Npop", false]], "ntimesteps (selfisys.hiddenbox.hiddenbox property)": [[4, "selfisys.hiddenbox.HiddenBox.Ntimesteps", false], [7, "selfisys.hiddenbox.HiddenBox.Ntimesteps", false]], "one_lognormal() (selfisys.selection_functions.lognormalselection static method)": [[4, "selfisys.selection_functions.LognormalSelection.one_lognormal", false], [11, "selfisys.selection_functions.LognormalSelection.one_lognormal", false]], "one_lognormal_z() (selfisys.selection_functions.lognormalselection static method)": [[4, "selfisys.selection_functions.LognormalSelection.one_lognormal_z", false], [11, "selfisys.selection_functions.LognormalSelection.one_lognormal_z", false]], "p (selfisys.setup_model.modelsetup attribute)": [[4, "selfisys.setup_model.ModelSetup.P", false], [13, "selfisys.setup_model.ModelSetup.P", false]], "p_0 (selfisys.setup_model.modelsetup attribute)": [[4, "selfisys.setup_model.ModelSetup.P_0", false], [13, "selfisys.setup_model.ModelSetup.P_0", false]], "p_ss_obj_path (selfisys.setup_model.modelsetup attribute)": [[4, "selfisys.setup_model.ModelSetup.P_ss_obj_path", false], [13, "selfisys.setup_model.ModelSetup.P_ss_obj_path", false]], "params_ids_to_simbelmyne_dict() (in module selfisys.utils.tools)": [[14, "selfisys.utils.tools.params_ids_to_Simbelmyne_dict", false]], "pbins (selfisys.setup_model.modelsetup attribute)": [[4, "selfisys.setup_model.ModelSetup.Pbins", false], [13, "selfisys.setup_model.ModelSetup.Pbins", false]], "pbins_bnd (selfisys.setup_model.modelsetup attribute)": [[4, "selfisys.setup_model.ModelSetup.Pbins_bnd", false], [13, "selfisys.setup_model.ModelSetup.Pbins_bnd", false]], "perform_prior_optimisation_and_plot() (in module selfisys.prior)": [[4, "selfisys.prior.perform_prior_optimisation_and_plot", false], [9, "selfisys.prior.perform_prior_optimisation_and_plot", false]], "planck_pk (selfisys.setup_model.modelsetup attribute)": [[4, "selfisys.setup_model.ModelSetup.planck_Pk", false], [13, "selfisys.setup_model.ModelSetup.planck_Pk", false]], "planck_prior (class in selfisys.prior)": [[4, "selfisys.prior.planck_prior", false], [9, "selfisys.prior.planck_prior", false]], "plot_c() (in module selfisys.utils.plot_utils)": [[14, "selfisys.utils.plot_utils.plot_C", false]], "plot_comoving_distance_redshift() (in module selfisys.utils.plot_examples)": [[14, "selfisys.utils.plot_examples.plot_comoving_distance_redshift", false]], "plot_fisher() (in module selfisys.utils.plot_utils)": [[14, "selfisys.utils.plot_utils.plot_fisher", false]], "plot_galaxy_field_slice() (in module selfisys.utils.plot_examples)": [[14, "selfisys.utils.plot_examples.plot_galaxy_field_slice", false]], "plot_gradients() (in module selfisys.utils.plot_utils)": [[14, "selfisys.utils.plot_utils.plot_gradients", false]], "plot_histogram() (in module selfisys.utils.plot_utils)": [[14, "selfisys.utils.plot_utils.plot_histogram", false]], "plot_mocks() (in module selfisys.utils.plot_utils)": [[14, "selfisys.utils.plot_utils.plot_mocks", false]], "plot_mocks_compact() (in module selfisys.utils.plot_utils)": [[14, "selfisys.utils.plot_utils.plot_mocks_compact", false]], "plot_observations() (in module selfisys.utils.plot_utils)": [[14, "selfisys.utils.plot_utils.plot_observations", false]], "plot_power_spectrum() (in module selfisys.utils.plot_examples)": [[14, "selfisys.utils.plot_examples.plot_power_spectrum", false]], "plot_prior_and_posterior_covariances() (in module selfisys.utils.plot_utils)": [[14, "selfisys.utils.plot_utils.plot_prior_and_posterior_covariances", false]], "plot_reconstruction() (in module selfisys.utils.plot_utils)": [[14, "selfisys.utils.plot_utils.plot_reconstruction", false]], "plot_selection_functions() (in module selfisys.utils.plot_utils)": [[14, "selfisys.utils.plot_utils.plot_selection_functions", false]], "plot_selection_functions_def_in_z() (in module selfisys.utils.plot_examples)": [[14, "selfisys.utils.plot_examples.plot_selection_functions_def_in_z", false]], "plotly_3d() (in module selfisys.utils.plot_utils)": [[14, "selfisys.utils.plot_utils.plotly_3d", false]], "prefix_mocks (selfisys.hiddenbox.hiddenbox property)": [[4, "selfisys.hiddenbox.HiddenBox.prefix_mocks", false], [7, "selfisys.hiddenbox.HiddenBox.prefix_mocks", false]], "primordial_grf() (in module selfisys.grf)": [[4, "selfisys.grf.primordial_grf", false], [6, "selfisys.grf.primordial_grf", false]], "printdiagnostic() (in module selfisys.utils.logger)": [[14, "selfisys.utils.logger.PrintDiagnostic", false]], "printerror() (in module selfisys.utils.logger)": [[14, "selfisys.utils.logger.PrintError", false]], "printinfo() (in module selfisys.utils.logger)": [[14, "selfisys.utils.logger.PrintInfo", false]], "printlefttype() (in module selfisys.utils.logger)": [[14, "selfisys.utils.logger.PrintLeftType", false]], "printmessage() (in module selfisys.selfi_interface)": [[4, "selfisys.selfi_interface.PrintMessage", false], [12, "selfisys.selfi_interface.PrintMessage", false]], "printwarning() (in module selfisys.utils.logger)": [[14, "selfisys.utils.logger.PrintWarning", false]], "psingle (selfisys.hiddenbox.hiddenbox property)": [[4, "selfisys.hiddenbox.HiddenBox.Psingle", false], [7, "selfisys.hiddenbox.HiddenBox.Psingle", false]], "r_grid() (selfisys.selection_functions.lognormalselection method)": [[4, "selfisys.selection_functions.LognormalSelection.r_grid", false], [11, "selfisys.selection_functions.LognormalSelection.r_grid", false]], "redshift_distance_conversion() (in module selfisys.utils.plot_examples)": [[14, "selfisys.utils.plot_examples.redshift_distance_conversion", false]], "relative_error_analysis() (in module selfisys.utils.plot_examples)": [[14, "selfisys.utils.plot_examples.relative_error_analysis", false]], "reset_plotting() (in module selfisys.utils.plot_params)": [[14, "selfisys.utils.plot_params.reset_plotting", false]], "reset_survey() (selfisys.hiddenbox.hiddenbox method)": [[4, "selfisys.hiddenbox.HiddenBox.reset_survey", false], [7, "selfisys.hiddenbox.HiddenBox.reset_survey", false]], "s (selfisys.setup_model.modelsetup attribute)": [[4, "selfisys.setup_model.ModelSetup.S", false], [13, "selfisys.setup_model.ModelSetup.S", false]], "safe_npload() (in module selfisys.utils.parser)": [[14, "selfisys.utils.parser.safe_npload", false]], "sample() (selfisys.prior.planck_prior method)": [[4, "selfisys.prior.planck_prior.sample", false], [9, "selfisys.prior.planck_prior.sample", false]], "sample_omega_from_prior() (in module selfisys.utils.tools)": [[14, "selfisys.utils.tools.sample_omega_from_prior", false]], "save() (selfisys.prior.planck_prior method)": [[4, "selfisys.prior.planck_prior.save", false], [9, "selfisys.prior.planck_prior.save", false]], "scalarformatterforceformat_11 (class in selfisys.utils.plot_params)": [[14, "selfisys.utils.plot_params.ScalarFormatterForceFormat_11", false]], "selfisys": [[4, "module-selfisys", false]], "selfisys.global_parameters": [[4, "module-selfisys.global_parameters", false], [5, "module-selfisys.global_parameters", false]], "selfisys.grf": [[4, "module-selfisys.grf", false], [6, "module-selfisys.grf", false]], "selfisys.hiddenbox": [[4, "module-selfisys.hiddenbox", false], [7, "module-selfisys.hiddenbox", false]], "selfisys.normalise_hb": [[4, "module-selfisys.normalise_hb", false], [8, "module-selfisys.normalise_hb", false]], "selfisys.prior": [[4, "module-selfisys.prior", false], [9, "module-selfisys.prior", false]], "selfisys.sbmy_interface": [[4, "module-selfisys.sbmy_interface", false], [10, "module-selfisys.sbmy_interface", false]], "selfisys.selection_functions": [[4, "module-selfisys.selection_functions", false], [11, "module-selfisys.selection_functions", false]], "selfisys.selfi_interface": [[4, "module-selfisys.selfi_interface", false], [12, "module-selfisys.selfi_interface", false]], "selfisys.setup_model": [[4, "module-selfisys.setup_model", false], [13, "module-selfisys.setup_model", false]], "selfisys.utils": [[14, "module-selfisys.utils", false]], "selfisys.utils.examples_utils": [[14, "module-selfisys.utils.examples_utils", false]], "selfisys.utils.logger": [[14, "module-selfisys.utils.logger", false]], "selfisys.utils.low_level": [[14, "module-selfisys.utils.low_level", false]], "selfisys.utils.parser": [[14, "module-selfisys.utils.parser", false]], "selfisys.utils.path_utils": [[14, "module-selfisys.utils.path_utils", false]], "selfisys.utils.plot_examples": [[14, "module-selfisys.utils.plot_examples", false]], "selfisys.utils.plot_params": [[14, "module-selfisys.utils.plot_params", false]], "selfisys.utils.plot_utils": [[14, "module-selfisys.utils.plot_utils", false]], "selfisys.utils.timestepping": [[14, "module-selfisys.utils.timestepping", false]], "selfisys.utils.tools": [[14, "module-selfisys.utils.tools", false]], "selfisys.utils.workers": [[14, "module-selfisys.utils.workers", false]], "set_scientific() (selfisys.utils.plot_params.scalarformatterforceformat_11 method)": [[14, "selfisys.utils.plot_params.ScalarFormatterForceFormat_11.set_scientific", false]], "set_useoffset() (selfisys.utils.plot_params.scalarformatterforceformat_11 method)": [[14, "selfisys.utils.plot_params.ScalarFormatterForceFormat_11.set_useOffset", false]], "setup_model() (in module selfisys.setup_model)": [[4, "selfisys.setup_model.setup_model", false], [13, "selfisys.setup_model.setup_model", false]], "setup_only (selfisys.hiddenbox.hiddenbox property)": [[4, "selfisys.hiddenbox.HiddenBox.setup_only", false], [7, "selfisys.hiddenbox.HiddenBox.setup_only", false]], "setup_plotting() (in module selfisys.utils.plot_params)": [[14, "selfisys.utils.plot_params.setup_plotting", false]], "setup_sbmy_parfiles() (in module selfisys.sbmy_interface)": [[4, "selfisys.sbmy_interface.setup_sbmy_parfiles", false], [10, "selfisys.sbmy_interface.setup_sbmy_parfiles", false]], "simbelmyne_worker() (in module selfisys.utils.workers)": [[14, "selfisys.utils.workers.Simbelmyne_worker", false]], "size (selfisys.setup_model.modelsetup attribute)": [[4, "selfisys.setup_model.ModelSetup.size", false], [13, "selfisys.setup_model.ModelSetup.size", false]], "stderr_redirector() (in module selfisys.utils.low_level)": [[14, "selfisys.utils.low_level.stderr_redirector", false]], "stdout_redirector() (in module selfisys.utils.low_level)": [[14, "selfisys.utils.low_level.stdout_redirector", false]], "summary_to_score() (in module selfisys.utils.tools)": [[14, "selfisys.utils.tools.summary_to_score", false]], "switch_recompute_pool() (selfisys.hiddenbox.hiddenbox method)": [[4, "selfisys.hiddenbox.HiddenBox.switch_recompute_pool", false], [7, "selfisys.hiddenbox.HiddenBox.switch_recompute_pool", false]], "switch_setup() (selfisys.hiddenbox.hiddenbox method)": [[4, "selfisys.hiddenbox.HiddenBox.switch_setup", false], [7, "selfisys.hiddenbox.HiddenBox.switch_setup", false]], "unindent() (in module selfisys.selfi_interface)": [[4, "selfisys.selfi_interface.unindent", false], [12, "selfisys.selfi_interface.unindent", false]], "unindent() (in module selfisys.utils.logger)": [[14, "selfisys.utils.logger.UNINDENT", false]], "update() (selfisys.hiddenbox.hiddenbox method)": [[4, "selfisys.hiddenbox.HiddenBox.update", false], [7, "selfisys.hiddenbox.HiddenBox.update", false]], "worker_class() (in module selfisys.prior)": [[4, "selfisys.prior.worker_class", false], [9, "selfisys.prior.worker_class", false]], "worker_gradient_symbelmyne() (in module selfisys.utils.workers)": [[14, "selfisys.utils.workers.worker_gradient_Symbelmyne", false]], "worker_normalisation() (in module selfisys.normalise_hb)": [[4, "selfisys.normalise_hb.worker_normalisation", false], [8, "selfisys.normalise_hb.worker_normalisation", false]], "worker_normalisation_public() (in module selfisys.normalise_hb)": [[4, "selfisys.normalise_hb.worker_normalisation_public", false], [8, "selfisys.normalise_hb.worker_normalisation_public", false]], "worker_normalisation_wrapper() (in module selfisys.normalise_hb)": [[4, "selfisys.normalise_hb.worker_normalisation_wrapper", false], [8, "selfisys.normalise_hb.worker_normalisation_wrapper", false]]}, "objects": {"": [[4, 0, 0, "-", "selfisys"]], "selfisys": [[5, 0, 0, "-", "global_parameters"], [6, 0, 0, "-", "grf"], [7, 0, 0, "-", "hiddenbox"], [8, 0, 0, "-", "normalise_hb"], [9, 0, 0, "-", "prior"], [10, 0, 0, "-", "sbmy_interface"], [11, 0, 0, "-", "selection_functions"], [12, 0, 0, "-", "selfi_interface"], [13, 0, 0, "-", "setup_model"], [14, 0, 0, "-", "utils"]], "selfisys.grf": [[6, 1, 1, "", "primordial_grf"]], "selfisys.hiddenbox": [[7, 2, 1, "", "HiddenBox"]], "selfisys.hiddenbox.HiddenBox": [[7, 3, 1, "", "Npop"], [7, 3, 1, "", "Ntimesteps"], [7, 3, 1, "", "Psingle"], [7, 4, 1, "", "compute_pool"], [7, 4, 1, "", "evaluate"], [7, 3, 1, "", "force_neglect_lightcone"], [7, 3, 1, "", "force_recompute_mocks"], [7, 3, 1, "", "gravity_on"], [7, 4, 1, "", "load_pool"], [7, 4, 1, "", "make_data"], [7, 3, 1, "", "modified_selfi"], [7, 3, 1, "", "prefix_mocks"], [7, 4, 1, "", "reset_survey"], [7, 3, 1, "", "setup_only"], [7, 4, 1, "", "switch_recompute_pool"], [7, 4, 1, "", "switch_setup"], [7, 4, 1, "", "update"]], "selfisys.normalise_hb": [[8, 1, 1, "", "define_normalisation"], [8, 1, 1, "", "worker_normalisation"], [8, 1, 1, "", "worker_normalisation_public"], [8, 1, 1, "", "worker_normalisation_wrapper"]], "selfisys.prior": [[9, 1, 1, "", "get_summary"], [9, 1, 1, "", "logposterior_hyperparameters_parallel"], [9, 1, 1, "", "perform_prior_optimisation_and_plot"], [9, 2, 1, "", "planck_prior"], [9, 1, 1, "", "worker_class"]], "selfisys.prior.planck_prior": [[9, 3, 1, "", "Nbin_max"], [9, 3, 1, "", "Nbin_min"], [9, 4, 1, "", "compute"], [9, 5, 1, "", "covariance"], [9, 5, 1, "", "inv_covariance"], [9, 4, 1, "", "load"], [9, 4, 1, "", "logpdf"], [9, 5, 1, "", "mean"], [9, 4, 1, "", "sample"], [9, 4, 1, "", "save"]], "selfisys.sbmy_interface": [[10, 1, 1, "", "compute_Phi"], [10, 1, 1, "", "generate_white_noise_Field"], [10, 1, 1, "", "get_power_spectrum_from_cosmo"], [10, 1, 1, "", "handle_time_stepping"], [10, 1, 1, "", "setup_sbmy_parfiles"]], "selfisys.selection_functions": [[11, 2, 1, "", "LognormalSelection"]], "selfisys.selection_functions.LognormalSelection": [[11, 4, 1, "", "init_selection"], [11, 4, 1, "", "lognormals_z_to_x"], [11, 4, 1, "", "multiple_lognormal"], [11, 4, 1, "", "multiple_lognormal_z"], [11, 4, 1, "", "one_lognormal"], [11, 4, 1, "", "one_lognormal_z"], [11, 4, 1, "", "r_grid"]], "selfisys.selfi_interface": [[12, 1, 1, "", "PrintMessage"], [12, 1, 1, "", "indent"], [12, 1, 1, "", "unindent"]], "selfisys.setup_model": [[13, 2, 1, "", "ModelSetup"], [13, 1, 1, "", "compute_alpha_cv"], [13, 1, 1, "", "setup_model"]], "selfisys.setup_model.ModelSetup": [[13, 5, 1, "", "G_sim_path"], [13, 5, 1, "", "G_ss_path"], [13, 5, 1, "", "L"], [13, 5, 1, "", "P"], [13, 5, 1, "", "P_0"], [13, 5, 1, "", "P_ss_obj_path"], [13, 5, 1, "", "Pbins"], [13, 5, 1, "", "Pbins_bnd"], [13, 5, 1, "", "S"], [13, 5, 1, "", "k_s"], [13, 5, 1, "", "planck_Pk"], [13, 5, 1, "", "size"]], "selfisys.utils": [[14, 0, 0, "-", "examples_utils"], [14, 0, 0, "-", "logger"], [14, 0, 0, "-", "low_level"], [14, 0, 0, "-", "parser"], [14, 0, 0, "-", "path_utils"], [14, 0, 0, "-", "plot_examples"], [14, 0, 0, "-", "plot_params"], [14, 0, 0, "-", "plot_utils"], [14, 0, 0, "-", "timestepping"], [14, 0, 0, "-", "tools"], [14, 0, 0, "-", "workers"]], "selfisys.utils.examples_utils": [[14, 1, 1, "", "clear_large_plot"]], "selfisys.utils.logger": [[14, 2, 1, "", "CustomLogger"], [14, 2, 1, "", "CustomLoggerHandler"], [14, 1, 1, "", "INDENT"], [14, 1, 1, "", "PrintDiagnostic"], [14, 1, 1, "", "PrintError"], [14, 1, 1, "", "PrintInfo"], [14, 1, 1, "", "PrintLeftType"], [14, 1, 1, "", "PrintWarning"], [14, 1, 1, "", "UNINDENT"], [14, 1, 1, "", "getCustomLogger"]], "selfisys.utils.logger.CustomLogger": [[14, 4, 1, "", "diagnostic"]], "selfisys.utils.logger.CustomLoggerHandler": [[14, 4, 1, "", "emit"]], "selfisys.utils.low_level": [[14, 1, 1, "", "stderr_redirector"], [14, 1, 1, "", "stdout_redirector"]], "selfisys.utils.parser": [[14, 1, 1, "", "bool_sh"], [14, 1, 1, "", "check_files_exist"], [14, 1, 1, "", "intNone"], [14, 1, 1, "", "joinstrs"], [14, 1, 1, "", "joinstrs_only"], [14, 1, 1, "", "none_or_bool_or_str"], [14, 1, 1, "", "safe_npload"]], "selfisys.utils.path_utils": [[14, 1, 1, "", "file_names_evaluate"], [14, 1, 1, "", "get_file_names"]], "selfisys.utils.plot_examples": [[14, 1, 1, "", "plot_comoving_distance_redshift"], [14, 1, 1, "", "plot_galaxy_field_slice"], [14, 1, 1, "", "plot_power_spectrum"], [14, 1, 1, "", "plot_selection_functions_def_in_z"], [14, 1, 1, "", "redshift_distance_conversion"], [14, 1, 1, "", "relative_error_analysis"]], "selfisys.utils.plot_params": [[14, 2, 1, "", "ScalarFormatterForceFormat_11"], [14, 1, 1, "", "create_all_colormaps"], [14, 1, 1, "", "create_colormap"], [14, 1, 1, "", "dynamic_text_scaling"], [14, 1, 1, "", "get_contours"], [14, 1, 1, "", "reset_plotting"], [14, 1, 1, "", "setup_plotting"]], "selfisys.utils.plot_params.ScalarFormatterForceFormat_11": [[14, 4, 1, "", "get_offset"], [14, 4, 1, "", "set_scientific"], [14, 4, 1, "", "set_useOffset"]], "selfisys.utils.plot_utils": [[14, 1, 1, "", "plot_C"], [14, 1, 1, "", "plot_fisher"], [14, 1, 1, "", "plot_gradients"], [14, 1, 1, "", "plot_histogram"], [14, 1, 1, "", "plot_mocks"], [14, 1, 1, "", "plot_mocks_compact"], [14, 1, 1, "", "plot_observations"], [14, 1, 1, "", "plot_prior_and_posterior_covariances"], [14, 1, 1, "", "plot_reconstruction"], [14, 1, 1, "", "plot_selection_functions"], [14, 1, 1, "", "plotly_3d"]], "selfisys.utils.timestepping": [[14, 1, 1, "", "merge_nTS"]], "selfisys.utils.tools": [[14, 1, 1, "", "cosmo_vector_to_Simbelmyne_dict"], [14, 1, 1, "", "cosmo_vector_to_class_dict"], [14, 1, 1, "", "custom_stat"], [14, 1, 1, "", "fisher_rao"], [14, 1, 1, "", "get_k_max"], [14, 1, 1, "", "get_summary"], [14, 1, 1, "", "none_or_bool_or_str"], [14, 1, 1, "", "params_ids_to_Simbelmyne_dict"], [14, 1, 1, "", "sample_omega_from_prior"], [14, 1, 1, "", "summary_to_score"]], "selfisys.utils.workers": [[14, 1, 1, "", "Simbelmyne_worker"], [14, 1, 1, "", "evaluate_gradient_of_Symbelmyne"], [14, 1, 1, "", "worker_gradient_Symbelmyne"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "function", "Python function"], "2": ["py", "class", "Python class"], "3": ["py", "property", "Python property"], "4": ["py", "method", "Python method"], "5": ["py", "attribute", "Python attribute"]}, "objtypes": {"0": "py:module", "1": "py:function", "2": "py:class", "3": "py:property", "4": "py:method", "5": "py:attribute"}, "terms": {"": [2, 4, 7, 10, 13, 14], "0": [2, 4, 6, 7, 8, 9, 10, 12, 13, 14], "000": [4, 9], "00065": [4, 9], "001": [4, 9], "0027": 14, "01": 14, "012": [4, 9], "015": [4, 9], "02": [4, 9, 14], "020": [4, 9], "04": [4, 8, 9], "04443": [1, 2, 3], "0455": 14, "05": [4, 9], "07": [4, 9], "08333333333333333": 14, "1": [2, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14], "10": [1, 2, 3, 4, 9, 13], "100": [4, 9, 13], "10000": [4, 9], "1093": 2, "11": [4, 13], "1111": 2, "12": [4, 9, 14], "128": 14, "1365": 2, "15": 2, "16610": 2, "16th": 14, "1e": [4, 9, 14], "2": [4, 6, 7, 10, 12, 13, 14], "2010": 2, "2015": 2, "2018": [4, 9, 13], "2019": 2, "2024": [1, 2, 3], "2024arxiv241204443h": [1, 2], "2412": [1, 2, 3], "2500": 14, "256": [4, 13], "2966": 2, "2d": [4, 9, 14], "3": [1, 2, 3, 4, 9, 13, 14], "30": [4, 9, 14], "3173": 14, "32nd": 14, "3600": [4, 13], "3d": [4, 11, 14], "4": [4, 9, 13, 14], "406": 2, "4237": 2, "4253": 2, "449": 2, "451": 2, "48550": [1, 2], "48th": 14, "490": 2, "5": [4, 10, 13, 14], "50": [4, 13], "539": 2, "580": 2, "6": [4, 13], "60": 2, "6666666666666666": 14, "7": [4, 9, 13], "8": [0, 4, 13], "848": 2, "85": 2, "866": 2, "9": [4, 13], "A": [1, 2, 3, 4, 8, 9, 13, 14], "By": [1, 3], "For": [0, 1, 3, 4, 9, 10, 14], "If": [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14], "It": [1, 3], "The": [1, 2, 3, 4, 6, 7, 9, 12, 14], "Will": 14, "aa": [4, 10], "ab": [1, 2], "abc": [1, 3, 4, 7, 14], "about": 0, "access": [4, 9], "accommod": [1, 3], "activ": 14, "actual": [0, 4, 12], "ad": [1, 2, 3], "add": 0, "addit": [4, 9], "address": [1, 3], "adher": 0, "adsab": [1, 2], "adsnot": [1, 2], "adsurl": [1, 2], "after": [4, 7, 9], "against": 14, "agre": [1, 3], "alan": 2, "alia": [4, 13], "alias": [4, 10], "aliasingcorr": [4, 10], "all": 14, "along": [1, 3, 4, 10, 14], "alongsid": [4, 7], "alpha": 14, "alpha_cv": [4, 9, 13], "alreadi": [4, 7], "altern": [4, 10], "among": [4, 10], "an": [0, 4, 6, 7, 9, 10, 14], "ani": [0, 4, 7, 8, 14], "annot": 14, "api": 1, "appear": 14, "append": 14, "appli": [4, 10], "applic": 0, "appropri": 0, "approxim": [1, 3], "ar": [0, 2, 4, 10, 14], "arbitrari": 14, "archiveprefix": [1, 2], "arg": [4, 8, 14], "argument": 14, "arrai": [4, 6, 8, 9, 10, 11, 14], "array_lik": [4, 11], "art": 2, "articl": [1, 2], "arxiv": [1, 2, 3], "ascend": [4, 10], "assum": 14, "astro": [1, 2, 3], "astronom": 2, "astrophys": [1, 2], "astrophysiqu": [1, 3], "astropi": 14, "attribut": [4, 11, 14], "attributeerror": [4, 11], "author": [0, 1, 2, 3], "auto": [4, 9], "automat": 14, "avail": [1, 4, 9], "averag": 14, "avoid": [0, 14], "ax": 14, "axi": [4, 10, 14], "b": [2, 14], "bar": [4, 7, 14], "base": [1, 3, 4, 7, 9, 10, 11, 13, 14], "baselin": [4, 10], "batch_idx": 14, "bayesian": [1, 2, 3], "befor": 0, "behaviour": 0, "being": 14, "below": 2, "best": 0, "between": 14, "bia": [1, 2, 3], "bibtex": [1, 2], "bin": [4, 8, 9, 13, 14], "binned_statist": 14, "bispectrum": 2, "black": 2, "blue": 14, "bool": [4, 6, 7, 8, 9, 10, 11, 13, 14], "bool_sh": [4, 14], "boolean": 14, "boss": 2, "bottom": 0, "bound": [4, 9], "boundari": [4, 13, 14], "box": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14], "branch": 0, "bug": 0, "c": [2, 4, 7, 11, 14], "c0_inv": 14, "c4": 14, "c5": 14, "c6": 14, "c7": 14, "c_0": 14, "calcul": 14, "call": [4, 7], "callabl": [4, 7], "can": 14, "cannot": [4, 6, 9, 14], "carlo": [1, 3], "case": 0, "catalogu": 2, "caution": 14, "cell": [0, 4, 6, 14], "centr": [4, 13, 14], "central": 14, "chainlevel": 14, "chang": 0, "check": [4, 7, 14], "check_files_exist": [4, 14], "check_output": [4, 7], "citat": [1, 2], "cite": [1, 2, 3], "clarif": 0, "class": [1, 3, 4, 7, 8, 9, 10, 11, 13, 14], "classi": 14, "classmethod": [4, 9], "clean": 0, "clear": [0, 14], "clear_large_plot": [4, 14], "clip": 14, "cluster": 2, "clutter": 14, "cmap": 14, "co": [1, 2, 3], "code": [1, 2, 3, 14], "coeff": 14, "coeffici": 14, "collabor": [1, 2, 3], "collect": 14, "color": 14, "colormap": 14, "colour": 14, "colours_list": 14, "com": [0, 14], "com_ob": 14, "comov": [4, 11, 13, 14], "compar": 14, "comparison": 14, "compat": [4, 7, 8, 9, 14], "complianc": [1, 3], "comprehens": [1, 3], "comput": [1, 3, 4, 7, 8, 9, 10, 11, 13, 14], "compute_alpha_cv": [4, 13], "compute_phi": [3, 4, 10], "compute_pool": [4, 7], "concaten": 14, "concis": 0, "confid": 14, "configur": 14, "conflevel": 14, "consist": 14, "constant": [4, 8, 9, 10, 14], "contact": [0, 1, 2, 3], "contain": [4, 8, 10, 11, 13, 14], "content": 3, "context": 14, "contour": [4, 9, 14], "contribut": 1, "control": [4, 9], "convers": 14, "convert": [4, 11, 14], "corner": 14, "correct": [4, 10], "correl": [4, 9], "correspond": 14, "corrupt": [4, 7], "cosmic": [4, 9, 13], "cosmo": [4, 7, 8, 10, 14], "cosmo_vect": [4, 7], "cosmo_vector_to_class_dict": [4, 14], "cosmo_vector_to_simbelmyne_dict": [4, 14], "cosmolog": [1, 3, 4, 7, 8, 9, 10, 14], "cosmologi": [1, 2, 4, 10, 13, 14], "covari": [4, 9, 14], "cpu": [4, 9], "creat": [0, 4, 10, 14], "create_all_colormap": [4, 14], "create_colormap": [4, 14], "cumul": [4, 10], "current": [4, 7, 12, 14], "custom": [1, 3, 4, 7, 9, 10, 14], "custom_stat": [4, 14], "customlogg": [4, 14], "customloggerhandl": [4, 14], "cutoff": [4, 9], "d": [1, 2, 3, 4, 7, 10, 14], "data": [1, 2, 3, 4, 6, 7, 9, 10, 14], "dbg": 14, "de": [1, 3], "debug": [0, 4, 10, 12, 14], "dec": [1, 2], "decemb": 2, "decim": 14, "default": [4, 6, 7, 8, 9, 10, 14], "defin": [4, 8, 11], "define_normalis": [3, 4, 8], "delta": 14, "delta_x": 14, "deltas_x": 14, "demonstr": [1, 3], "denomin": 14, "densiti": [4, 6, 7, 14], "depend": [1, 3], "deriv": [4, 10, 14], "describ": 0, "design": [1, 3], "detail": [0, 1, 4, 6, 10, 14], "detect": [4, 9], "determinist": [4, 7], "develop": [1, 3], "deviat": [4, 9, 11, 14], "df_16_full": 14, "df_32_full": 14, "df_48_full": 14, "df_full": 14, "diagnos": [1, 2, 3], "diagnosi": [1, 3], "diagnost": [4, 14], "diagon": 14, "dict": [4, 7, 8, 10, 13, 14], "dictionari": [4, 7, 10, 14], "differ": 14, "differenti": 14, "dimens": [4, 6, 9, 10, 14], "direct": [4, 7, 10, 13, 14], "directori": [4, 7, 10, 13, 14], "discuss": 0, "disk": [4, 7, 10], "displai": [4, 7, 9, 12, 14], "distanc": [4, 11, 14], "distort": [2, 4, 7], "distribut": [1, 3, 4, 9, 10, 11, 14], "divid": [4, 9], "document": [0, 4, 10], "doe": 14, "doi": [1, 2], "doubl": [4, 10], "download": [1, 3], "dr11": 2, "draw": [4, 9, 14], "drawn": [4, 9], "dure": [4, 6, 9, 10, 14], "dust": [1, 3], "dw_f0": 14, "dynam": 14, "dynamic_text_sc": [4, 14], "e": [0, 1, 2, 3, 4, 10, 14], "each": [4, 7, 9, 10, 11, 13, 14], "edu": [1, 2], "eff_redshift": [4, 7, 10, 14], "effect": [0, 2, 4, 10], "eid": [1, 2], "element": [4, 13, 14], "elfi": [1, 3], "emit": [4, 14], "empti": 14, "enabl": [1, 3, 4, 7], "enforc": 14, "enforce_ylim": 14, "ensur": [0, 1, 3, 14], "entri": [1, 2], "enzi": 2, "en\u00dflin": 2, "ep": 14, "eprint": [1, 2], "eps_k": [4, 9], "eps_residu": [4, 9], "equat": [4, 9], "error": [0, 4, 6, 9, 14], "estim": 14, "etc": 14, "evalu": [4, 7, 9, 11, 14], "evaluate_gradient_of_symbelmyn": [4, 14], "even": [4, 6, 7, 10, 14], "everi": 14, "evolut": [1, 3], "exampl": [1, 3, 14], "examples_util": [3, 4], "except": [4, 7], "exist": [4, 6, 7, 9, 10, 14], "expans": [1, 3, 4, 10, 14], "expect": [0, 14], "explain": 0, "extinct": [1, 3], "f": [1, 2, 3], "f0": 14, "f0_inv": 14, "f_0": 14, "factor": [4, 10, 11, 14], "fail": [4, 9, 10, 14], "failur": 14, "fals": [4, 6, 7, 8, 10, 11, 13, 14], "feedback": [1, 2, 3], "feel": [0, 1, 2, 3], "fetch": 0, "fiduci": [4, 9, 14], "field": [1, 3, 4, 6, 7, 10, 13, 14], "fig": 14, "fig_height": 14, "figur": 14, "figuresdir": [4, 10], "file": [1, 3, 4, 6, 7, 9, 10, 13, 14], "file_nam": [4, 10], "file_names_evalu": [4, 14], "filenam": [4, 7, 9, 14], "final": [4, 10], "find": 0, "finit": 14, "fisher": 14, "fisher_rao": [4, 14], "fit": [4, 9], "fix": [0, 4, 7, 14], "fixnois": [4, 7], "fixscal": 14, "float": [4, 6, 7, 8, 9, 10, 11, 13, 14], "florent": [1, 2], "fname": [4, 9], "fname_outputinitialdens": [4, 6], "fname_power_spectrum": [4, 10], "fname_powerspectrum": [4, 6], "fname_whitenois": [4, 10], "follow": [0, 1, 2, 3], "font": 14, "font_color": 14, "forc": [4, 7, 8, 10, 13, 14], "force_cosmo": [4, 7], "force_neglect_lightcon": [4, 7], "force_parfil": [4, 7], "force_phas": [4, 7, 10], "force_plot": 14, "force_powerspectrum": [4, 7], "force_recompute_mock": [4, 7], "force_sim": [4, 6, 7], "fork": 0, "format": 14, "formatt": 14, "forward": [1, 3, 4, 7], "fourier": [4, 7, 10, 13, 14], "fouriergrid": [4, 10, 14], "fr": [1, 3], "free": [0, 1, 2, 3, 14], "from": [0, 2, 4, 6, 7, 9, 10, 11, 14], "fsimdir": [4, 7, 14], "full": [4, 7, 13, 14], "fulli": [1, 3], "function": [0, 1, 3, 4, 7, 8, 9, 11, 13, 14], "further": 0, "futur": [1, 3, 4, 7], "g": [0, 1, 3, 4, 7, 10, 14], "g_obj": [4, 10], "g_sim": 14, "g_sim_path": [4, 7, 13, 14], "g_ss_path": [4, 7, 10, 13, 14], "galaxi": [2, 4, 7, 14], "gamma": 14, "gaussian": [4, 6, 9], "gener": [1, 3, 4, 6, 7, 9, 10, 11, 14], "generate_white_noise_field": [3, 4, 10], "get": 14, "get_contour": [4, 14], "get_file_nam": [4, 14], "get_k_max": [4, 14], "get_offset": [4, 14], "get_pk": [4, 9], "get_power_spectrum_from_cosmo": [3, 4, 10], "get_summari": [3, 4, 9, 14], "getcustomlogg": [4, 14], "gil": 2, "gil2015pow": 2, "git": 0, "github": [0, 1], "given": [4, 7, 9, 14], "go": 14, "gpc": 14, "gplv3": [1, 3], "gradient": [4, 7, 14], "gravit": [1, 3], "graviti": [1, 2, 3, 4, 7, 14], "gravity_on": [4, 7, 14], "grf": 3, "grid": [4, 6, 7, 10, 11, 13, 14], "ground": 14, "growth": 2, "guess": [4, 9], "guid": 0, "guidelin": 3, "h": [2, 4, 6, 9, 10, 13, 14], "handle_time_step": [3, 4, 10], "handler": 14, "harvard": [1, 2], "have": 0, "hdf5": [4, 9], "heatmap": 14, "heaven": 2, "height": 14, "hello": 14, "hidden": [1, 3, 4, 7], "hidden_box": [4, 8], "hiddenbox": [1, 3, 8, 10], "hiddenbox_param": [4, 10], "hierarch": [1, 3], "highest": 0, "highlight": 14, "histogram": 14, "hoellin": [0, 1], "hoelling": [1, 2, 3], "hoellinger2024diagnos": [1, 2, 4, 9], "how": [1, 3], "howlett": 2, "howlett2015clust": 2, "hpc": [4, 9], "http": [0, 1, 2], "hyperparamet": [4, 9, 14], "hyperprior": [4, 9], "i": [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 14], "iap": [1, 3], "id": [4, 7, 14], "id_ob": 14, "identifi": [4, 7, 14], "ignor": 14, "ii": 2, "implement": [1, 3], "implicit": [1, 3, 14], "improv": 0, "inaccur": [1, 3], "inch": 14, "includ": [0, 1, 4, 10, 14], "incorpor": [0, 1, 3], "indent": [3, 4, 12, 14], "index": [4, 7, 8, 9, 10, 14], "indic": [4, 7, 10, 14], "indices_steps_cumul": [4, 10, 14], "individu": [4, 10, 14], "infer": [1, 2, 3, 14], "inform": [1, 14], "infrastructur": [4, 7, 8, 10], "init_select": [4, 11], "initi": [1, 2, 3, 4, 6, 7, 9, 10], "initialis": [4, 7, 11], "input": [4, 6, 7, 9, 10, 11, 13, 14], "inquiri": [1, 2, 3], "instal": [1, 3], "instanc": [4, 8, 14], "instead": 14, "institut": [1, 3], "instruct": [1, 3], "instrument": [1, 2], "insuffici": 14, "int": [4, 6, 7, 8, 9, 10, 12, 13, 14], "integr": [4, 7], "interact": 14, "interlop": [1, 3], "interpol": [4, 11, 14], "intnon": [4, 14], "inv_covari": [4, 9], "invalid": [4, 9, 10, 14], "invers": [4, 9, 14], "io": 1, "issu": [1, 2, 3, 4, 10], "its": [4, 11, 14], "j": 2, "jasch": 2, "jasche2010bayesian": [2, 14], "jen": 2, "join": 14, "joinstr": [4, 14], "joinstrs_onli": [4, 14], "journal": [1, 2], "k": [4, 8, 13, 14], "k_": [4, 7, 13, 14], "k_corr": [4, 9], "k_corr_max": [4, 9], "k_corr_mean": [4, 9], "k_corr_min": [4, 9], "k_corr_std": [4, 9], "k_min": [4, 9], "k_mode": 14, "k_opt_max": [4, 9], "k_opt_min": [4, 9], "kei": 14, "keyword": [1, 2], "kitaura": 2, "kmax": [4, 9, 14], "kwarg": [4, 7, 14], "l": [2, 4, 6, 7, 10, 11, 13, 14], "label": 14, "labsab": 14, "larg": [1, 2, 3, 4, 7], "latest": 0, "lcorner": 14, "leav": [0, 14], "leclercq": [1, 2, 3], "leclercq2019primordi": [2, 4, 9, 14], "legend": 14, "legend_loc": 14, "length": [4, 6, 13, 14], "level": [4, 6, 7, 10, 12, 14], "leverag": [1, 3], "licenc": [1, 3], "lightcon": [4, 7], "like": [4, 9, 11, 14], "likelihood": [1, 3, 14], "limit": 14, "lin_bia": 14, "line": [1, 3, 14], "linear": [1, 3, 4, 11, 14], "linear_bia": [4, 7], "list": [1, 2, 3, 4, 7, 8, 10, 11, 14], "list_of_str": 14, "listedcolormap": 14, "ll": [4, 11], "lmax": 14, "load": [4, 7, 9, 13, 14], "load_pool": [4, 7], "local": 0, "local_mask_prefix": [4, 7, 14], "local_select_path": [4, 11], "locat": 14, "log": [0, 4, 9, 11, 14], "logger": [3, 4], "lognorm": [4, 11, 14], "lognormals_z_to_x": [4, 11], "lognormalselect": [3, 4, 11], "logpdf": [4, 9], "logposterior_hyperparameters_parallel": [3, 4, 9], "low_level": [3, 4], "lower": [4, 9], "m": 2, "mahalanobi": 14, "main": [0, 2], "maintain": [1, 3], "make_data": [4, 7], "manag": [4, 7, 14], "manera": 2, "map": 14, "markdown": 0, "mar\u00edn": 2, "mask": [1, 3, 4, 7, 11], "match": [4, 13], "matplotlib": 14, "matric": 14, "matrix": [4, 9, 14], "matter": [1, 3], "max": 14, "maxim": [4, 9], "maximum": [4, 9, 11, 13, 14], "maxval": [4, 13], "md": 1, "mean": [4, 9, 11, 14], "means_com": 14, "measur": 2, "memori": 14, "merg": [0, 4, 10, 14], "merge_nt": [4, 14], "merged_path": [4, 10, 14], "mesh": [4, 9], "meshsiz": [4, 9], "messag": [0, 4, 12, 14], "message_typ": 14, "method": [1, 2, 4, 9, 14], "mi": 14, "min_k_norma": [4, 8], "minim": [4, 9], "minimum": [4, 8, 9, 13], "minmax": 14, "minval": [4, 13], "misspecif": [1, 3], "misspecifi": [1, 3], "mm": [4, 11], "mnra": 2, "mock": [2, 4, 7, 10, 14], "mode": [4, 7, 10, 13, 14], "model": [1, 3, 4, 7, 13, 14], "modeldir": [4, 10, 14], "modelsetup": [4, 13], "modifi": [4, 7, 14], "modified_selfi": [4, 7], "modul": 3, "mont": [1, 3], "month": [1, 2], "monthli": 2, "more": [4, 10], "most": [4, 7], "mpc": [4, 6, 10, 13, 14], "msg": 14, "mu": [4, 11], "much": 0, "multi": [4, 9], "multipl": [1, 3, 4, 11, 14], "multiple_lognorm": [4, 11], "multiple_lognormal_z": [4, 11], "multipol": 14, "multiprocess": [4, 8, 9], "n": [4, 6, 7, 8, 11, 14], "n_": [4, 9], "n_exact": [4, 13], "name": [4, 7, 10, 13, 14], "namedtupl": [4, 13], "nan": 14, "nasa": [1, 2], "nbin": 14, "nbin_max": [4, 9], "nbin_min": [4, 9], "ndarrai": [4, 6, 7, 8, 9, 10, 11, 13, 14], "necessari": [4, 13], "need": 0, "neg": 14, "neglect": [4, 7], "new": 0, "nograv": [4, 10], "nois": [4, 7, 10, 14], "noise_std": [4, 7], "non": [1, 3, 14], "none": [4, 6, 7, 9, 10, 11, 12, 13, 14], "none_or_bool_or_str": [4, 14], "nongalact": [1, 2], "norm": [4, 10, 14], "norm_cst": [4, 7, 8, 14], "normal": [4, 10, 11, 12], "normalis": [4, 8, 9, 10, 11, 13, 14], "normalise_hb": 3, "notat": 14, "notic": 2, "notimplementederror": [4, 10], "np": [4, 13, 14], "np0": [4, 7, 14], "npar": [4, 8], "npm0": [4, 7, 14], "npop": [4, 7, 14], "npy": [4, 9], "nsampl": [4, 9, 14], "nthread": [4, 9], "ntimestep": [4, 7], "nuisanc": [4, 7], "number": [4, 6, 7, 8, 9, 10, 13, 14], "numpi": [4, 6, 14], "o": 14, "object": [4, 7, 9, 10, 11, 14], "obs_dens": 14, "observ": [1, 3, 4, 7, 14], "observed_dens": [4, 7], "occur": [4, 6, 10, 14], "offset": 14, "omega": 14, "omega0": 14, "omega_b": [4, 9], "omega_cov": [4, 9, 14], "omega_m": [4, 9], "omega_mean": [4, 9, 14], "omegas_fix": 14, "one": 14, "one_lognorm": [4, 11], "one_lognormal_z": [4, 11], "onli": [4, 7, 14], "open": 0, "oper": [4, 9, 10], "opportun": [1, 2, 3], "optimis": [4, 9], "option": [4, 6, 7, 8, 9, 10, 11, 13, 14], "order": [4, 10, 14], "org": 2, "origin": [4, 11], "oserror": [4, 6, 9, 10, 14], "other": [1, 2, 3], "otherwis": [4, 6, 7, 9, 10, 14], "oup": 2, "output": [0, 4, 6, 7, 9, 10, 12, 14], "over": 14, "overal": [4, 9], "overlai": 14, "overridden": 14, "overwrit": [4, 7], "p": [4, 7, 8, 13, 14], "p_0": [4, 13, 14], "p_ss_obj_path": [4, 13, 14], "p_ss_path": [4, 7, 10], "packag": [1, 2, 3], "page": [1, 2], "paper": [1, 2, 3], "parallel": [4, 8, 9], "param": [4, 8, 9, 11, 14], "param_id": 14, "param_index": 14, "param_v": 14, "paramet": [1, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14], "params_id": 14, "params_ids_to_simbelmyne_dict": [4, 14], "params_names_fish": 14, "params_p0": [4, 13], "params_planck": [4, 13], "params_v": 14, "pari": [1, 3], "parser": [3, 4], "particular": 1, "path": [4, 6, 7, 9, 10, 13, 14], "path_util": [3, 4], "pbin": [4, 8, 13, 14], "pbins_bnd": [4, 7, 13, 14], "pdf": [1, 2, 3], "pep": 0, "per": [4, 6, 13], "perciv": 2, "perform": [1, 3], "perform_prior_optimisation_and_plot": [3, 4, 9], "ph": [1, 2, 3], "phase": [4, 6, 7, 10], "phi": [4, 7, 8, 10, 14], "phi_0": 14, "phi_ob": 14, "physic": [4, 10, 14], "pinit": [4, 13], "pipelin": [2, 4, 9], "place": 14, "planck": [4, 9, 13, 14], "planck_pk": [4, 13, 14], "planck_pk_eh": 14, "planck_prior": [3, 4, 9], "pleas": [0, 1, 2, 3, 4, 10], "plot": [4, 9, 10, 14], "plot_c": [4, 14], "plot_comoving_distance_redshift": [4, 14], "plot_exampl": [3, 4], "plot_fish": [4, 14], "plot_galaxy_field_slic": [4, 14], "plot_gradi": [4, 14], "plot_histogram": [4, 14], "plot_mock": [4, 14], "plot_mocks_compact": [4, 14], "plot_observ": [4, 14], "plot_param": [3, 4], "plot_power_spectrum": [4, 14], "plot_prior_and_posterior_covari": [4, 14], "plot_reconstruct": [4, 14], "plot_selection_funct": [4, 14], "plot_selection_functions_def_in_z": [4, 14], "plot_util": [3, 4], "plotli": 14, "plotly_3d": [4, 14], "pmc": [1, 3, 4, 7], "point": [4, 9, 10, 14], "pool": [4, 7], "pool_fnam": [4, 7], "poolname_abc": 14, "popul": [1, 3, 4, 7, 14], "possibl": 0, "posterior": [4, 9, 14], "posterior_theta_covari": 14, "posterior_theta_mean": 14, "power": [1, 2, 3, 4, 6, 7, 9, 10, 13, 14], "powerspectra": [4, 13], "powerspectrum": 14, "pp": 2, "practic": [0, 1, 3], "precomput": [4, 9], "prefix": [4, 7, 14], "prefix_mock": [4, 7, 14], "primaryclass": [1, 2], "primordi": 2, "primordial_grf": [3, 4, 6], "princip": [1, 3], "print": [0, 1, 2, 3, 4, 12, 14], "printdiagnost": [4, 14], "printerror": [4, 14], "printinfo": [4, 14], "printlefttyp": [4, 14], "printmessag": [3, 4, 12], "printwarn": [4, 14], "prior": [1, 3, 14], "prior_covari": 14, "prior_theta_covari": 14, "prior_theta_mean": 14, "probabilist": [1, 3], "probabl": [4, 9], "process": [4, 8, 9], "progress": [4, 6, 7], "properti": [4, 7, 9], "provid": [1, 2, 3, 4, 9, 10, 14], "psingl": [4, 7], "pull": 0, "pysbmi": 14, "pyselfi": [1, 3, 4, 7, 12], "python": [1, 3, 4, 8, 9, 14], "quantiti": 14, "question": 3, "quiet": [4, 10, 12], "r": [4, 7], "r_grid": [4, 11], "radial": [4, 7, 11, 14], "radial_select": [4, 7, 14], "rais": [4, 6, 9, 10, 11, 14], "random": [4, 6, 9, 10, 14], "rang": [4, 9, 14], "rao": 14, "rdylbu": 14, "re": [4, 7, 14], "read": [4, 6, 9, 10, 14], "real": [1, 3], "realis": [4, 7, 8, 10, 14], "realist": [1, 3], "recmah": 14, "recognis": 14, "recomput": [4, 7, 8, 10, 13], "reconstruct": 14, "record": 14, "redirect": 14, "redshift": [2, 4, 7, 10, 11, 14], "redshift_distance_convers": [4, 14], "refer": [0, 4, 10, 14], "regener": [4, 6, 10], "regularis": [4, 9], "rel": 14, "relat": [4, 11, 14], "relative_error_analysi": [4, 14], "releas": [1, 3], "relev": [0, 2], "remain": 14, "remot": 0, "remov": [4, 7], "remove_sbmi": [4, 7], "report": 3, "repositori": [0, 1, 3], "repres": [4, 7, 14], "represent": 14, "reproduc": [0, 1, 4, 6], "request": [0, 14], "requir": [4, 7, 8, 12, 13, 14], "required_verbos": [4, 12], "rerun": [4, 7], "res_mi": 14, "rescal": [4, 11], "research": [1, 2, 3], "reset": [4, 7, 11], "reset_plot": [4, 14], "reset_survei": [4, 7], "resolut": [4, 6], "respect": [4, 7, 14], "restrict": [4, 9], "result": [0, 4, 7, 9, 13, 14], "return": [4, 6, 7, 8, 9, 10, 11, 13, 14], "return_g": [4, 6, 7, 14], "review": [1, 3], "right": 14, "rng": [4, 10], "ross": 2, "routin": [4, 7, 14], "royal": 2, "rr": [4, 11], "rsd": [4, 7], "run": [0, 4, 7, 8], "runnabl": 0, "runtimeerror": [4, 6, 9, 10, 14], "safe_npload": [4, 14], "sampl": [2, 4, 9, 14], "sample_omega_from_prior": [4, 14], "sampler": [1, 3], "samushia": 2, "sao": [1, 2], "save": [4, 9, 10, 14], "save_frequ": [4, 7], "savepath": [4, 9, 14], "sbmy_interfac": 3, "scalar": 14, "scalarformatterforceformat_11": [4, 14], "scale": [1, 2, 3, 4, 7, 9, 10, 14], "scheme": 14, "scipi": 14, "score": 14, "screen": 14, "sd": 14, "sdss": 2, "see": [0, 4, 9, 10], "seed": [4, 6, 7, 9, 10, 14], "seedname_whitenois": [4, 10], "seednois": [4, 7, 8], "seednoise_init": 14, "seednorm": [4, 7], "seedphas": [4, 6, 7, 8, 10], "seedphase_init": 14, "seedsampl": [4, 9], "select": [1, 3, 4, 7, 11, 14], "selection_funct": 3, "selection_param": [4, 7, 11, 14], "self": [4, 7, 9], "selfi": [4, 7, 9], "selfi2019": [4, 9], "selfi_interfac": 3, "selfisi": [2, 6, 7, 8, 9, 10, 11, 12, 13], "selfisys_publ": [0, 1], "set": [4, 7, 10, 13, 14], "set_scientif": [4, 14], "set_useoffset": [4, 14], "setup": [4, 7, 10], "setup_onli": [4, 7, 14], "setup_plot": [4, 14], "setup_sbmy_parfil": [3, 4, 10], "should": 14, "show": [4, 9], "side": [4, 6, 14], "sig2": [4, 11], "sigma_8": [4, 9], "silent": [4, 6, 14], "sim_id": 14, "sim_param": [4, 7, 10, 14], "simbelmyne_work": [4, 14], "simbelmyn\u00eb": [1, 3, 4, 7, 10], "simdir": 14, "similar": [4, 9], "simspath": [4, 7], "simul": [1, 3, 4, 6, 7, 8, 10, 11, 14], "singl": [4, 7, 9, 14], "size": [4, 7, 10, 11, 13, 14], "slice": 14, "societi": 2, "softwar": [1, 3], "solver": [1, 3], "some": [4, 8, 14], "sourc": [4, 6, 7, 8, 9, 10, 11, 12, 13, 14], "space": [2, 4, 7, 9, 10, 14], "specif": [1, 14], "specifi": [4, 6, 10, 14], "spectra": [4, 9, 13, 14], "spectroscop": [1, 3, 4, 7], "spectrum": [1, 2, 3, 4, 6, 7, 9, 10, 13, 14], "spline": [4, 11, 14], "splitlpt": [4, 10], "sqrt": 14, "ss": [4, 11], "standard": [0, 4, 9, 11, 12, 14], "stat": 14, "state": [4, 10], "statement": 0, "static": [4, 11], "statist": [1, 3, 4, 7, 8, 10, 14], "std": [4, 10, 11], "stderr": 14, "stderr_redirector": [4, 14], "stdout": 14, "stdout_redirector": [4, 14], "stds_z": 14, "step": [0, 4, 7, 10, 14], "store": [4, 6, 7, 9, 10, 13], "str": [4, 6, 7, 8, 9, 10, 12, 13, 14], "strategi": [4, 10], "stream": 14, "strength": [4, 9], "string": [4, 10, 14], "structur": 2, "stu2693": 2, "stv961": 2, "style": 3, "stz2718": 2, "submit": 3, "submodul": 3, "suffix": [4, 7], "suggest": 0, "summari": [4, 7, 8, 9, 10, 13, 14], "summary_to_scor": [4, 14], "support": [4, 7, 13, 14], "suptitl": 14, "survei": [2, 4, 7, 11], "survey_mask_path": [4, 7, 11, 14], "switch_recompute_pool": [4, 7], "switch_setup": [4, 7], "synchronis": 0, "synthet": [1, 3], "system": [1, 2], "systemat": 2, "t": [1, 2, 3], "task": [4, 9], "ten": 14, "term": [1, 3, 4, 9], "test": 0, "text": 14, "th": [4, 8], "theta": [4, 7, 9, 14], "theta2p": [4, 7], "theta_covari": [4, 9], "theta_fid": 14, "theta_fiduci": [4, 9], "theta_gt": 14, "theta_icov": [4, 9], "theta_is_p": [4, 7], "theta_mean": [4, 9], "theta_norm": [4, 9], "theta_norm_max": [4, 9], "theta_norm_mean": [4, 9], "theta_norm_min": [4, 9], "theta_norm_std": [4, 9], "thi": [1, 3, 4, 7, 9, 14], "thorough": [1, 3], "thoroughli": 0, "thread": [4, 9], "tick": 14, "time": [4, 7, 10, 14], "timestep": [3, 4, 7], "timestepdistribut": [4, 7, 14], "tip": 0, "titl": [1, 2, 14], "toggl": [4, 7], "tool": [3, 4], "top": 0, "total": [4, 7, 8, 10], "total_step": [4, 10], "tqdm": 14, "transpar": 14, "tri": [4, 9], "trim_threshold": [4, 13], "tristan": [1, 2, 3], "true": [4, 6, 7, 8, 9, 10, 13, 14], "true_p": 14, "truncat": 14, "truth": 14, "ts_path_list": 14, "tune": 14, "tupl": [4, 8, 9, 10, 11, 13, 14], "two": 14, "type": [4, 6, 7, 8, 9, 10, 11, 13, 14], "typic": 14, "ui": [1, 2], "uncertainti": [4, 9], "unchang": 14, "under": [1, 3], "understand": [1, 3], "unexpect": [4, 6, 9, 10, 14], "unexpectedli": [4, 9], "unind": [3, 4, 12, 14], "unit": 14, "univariatesplin": [4, 11, 14], "unless": 14, "unnecessari": 0, "unnormalis": [4, 7, 14], "unpack": 14, "unsupport": [4, 10], "unsur": 0, "up": [4, 7, 10, 13, 14], "updat": [4, 7, 14], "upper": [4, 9, 14], "upstream": 0, "us": [0, 1, 2, 3, 4, 7, 8, 9, 10, 12, 14], "uselocal": 14, "usemathtext": 14, "useoffset": 14, "user": [4, 10], "util": [3, 4, 12], "v": [0, 14], "valid": [0, 14], "valu": [4, 7, 8, 9, 11, 13, 14], "valueerror": 14, "vari": 14, "varianc": [4, 9, 11, 13], "vec": 14, "vector": [4, 7, 9, 10, 14], "verbos": [4, 6, 7, 10, 12, 14], "version": 1, "vertic": 14, "visit": [1, 3], "visualis": 14, "vol": 2, "volum": [2, 14], "w": 2, "wandelt": 2, "warn": 14, "wavenumb": [4, 7, 9, 13, 14], "wd": 14, "we": [0, 1, 3], "welcom": 0, "well": [0, 14], "when": [0, 4, 7, 14], "where": [4, 7, 9, 13, 14], "whether": [0, 4, 7, 10, 11, 14], "which": [4, 10, 11, 14], "white": [4, 7, 10, 14], "window": [4, 13], "window_fct_path": [4, 13], "within": [4, 9], "without": 0, "wolfgang": 2, "work": 14, "workdir": [4, 13], "worker": [3, 4, 8, 9], "worker_class": [3, 4, 9], "worker_gradient_symbelmyn": [4, 14], "worker_normalis": [3, 4, 8], "worker_normalisation_publ": [3, 4, 8], "worker_normalisation_wrapp": [3, 4, 8], "world": [1, 3, 14], "wrapper": [4, 8], "writabl": [4, 9], "write": [4, 9, 10, 14], "written": [1, 3, 4, 9], "wrt": 14, "x": [2, 4, 9, 11, 14], "xx": [4, 11, 14], "xx_of_z": 14, "y": 14, "yaml": 1, "year": [1, 2], "you": [0, 1, 2, 3], "your": [0, 1, 2, 3], "z": [2, 14], "z_l": 14, "z_mean": 14, "zcorner": 14, "zz": 14, "\u03c6": 14}, "titles": ["Contributing to SelfiSys", "SelfiSys: Assess the Impact of Systematic Effects in Galaxy Surveys", "References", "SelfiSys: Assess the Impact of Systematic Effects in Galaxy Surveys", "selfisys package", "global_parameters", "grf", "hiddenbox", "normalise_hb", "prior", "sbmy_interface", "selection_functions", "selfi_interface", "setup_model", "selfisys.utils package"], "titleterms": {"api": 3, "assess": [1, 3], "code": 0, "content": [4, 14], "contribut": [0, 3], "contributor": [1, 3], "document": [1, 3], "effect": [1, 3], "examples_util": 14, "featur": [1, 3], "galaxi": [1, 3], "global_paramet": [4, 5], "grf": [4, 6], "guidelin": 0, "hiddenbox": [4, 7], "impact": [1, 3], "issu": 0, "jupyt": 0, "kei": [1, 3], "licens": [1, 3], "logger": 14, "low_level": 14, "modul": [4, 14], "normalise_hb": [4, 8], "notebook": 0, "packag": [4, 14], "parser": 14, "path_util": 14, "plot_exampl": 14, "plot_param": 14, "plot_util": 14, "prior": [4, 9], "python": 0, "question": 0, "refer": [1, 2, 3], "report": 0, "requir": [1, 3], "sbmy_interfac": [4, 10], "selection_funct": [4, 11], "selfi_interfac": [4, 12], "selfisi": [0, 1, 3, 4, 14], "setup_model": [4, 13], "style": 0, "submit": 0, "submodul": [4, 14], "subpackag": 4, "survei": [1, 3], "systemat": [1, 3], "timestep": 14, "tool": 14, "util": 14, "worker": 14}}) \ No newline at end of file diff --git a/docs/selfisys.global_parameters.html b/docs/selfisys.global_parameters.html new file mode 100644 index 0000000..e089cb2 --- /dev/null +++ b/docs/selfisys.global_parameters.html @@ -0,0 +1,311 @@ + + + + + + + + + global_parameters — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/selfisys.grf.html b/docs/selfisys.grf.html new file mode 100644 index 0000000..e656583 --- /dev/null +++ b/docs/selfisys.grf.html @@ -0,0 +1,351 @@ + + + + + + + + + grf — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/selfisys.hiddenbox.html b/docs/selfisys.hiddenbox.html new file mode 100644 index 0000000..7ec9369 --- /dev/null +++ b/docs/selfisys.hiddenbox.html @@ -0,0 +1,562 @@ + + + + + + + + + hiddenbox — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/selfisys.html b/docs/selfisys.html new file mode 100644 index 0000000..febbc76 --- /dev/null +++ b/docs/selfisys.html @@ -0,0 +1,1570 @@ + + + + + + + + + selfisys package — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/selfisys.normalise_hb.html b/docs/selfisys.normalise_hb.html new file mode 100644 index 0000000..9a0fbd0 --- /dev/null +++ b/docs/selfisys.normalise_hb.html @@ -0,0 +1,396 @@ + + + + + + + + + normalise_hb — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/selfisys.prior.html b/docs/selfisys.prior.html new file mode 100644 index 0000000..ca36bc6 --- /dev/null +++ b/docs/selfisys.prior.html @@ -0,0 +1,610 @@ + + + + + + + + + prior — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/selfisys.sbmy_interface.html b/docs/selfisys.sbmy_interface.html new file mode 100644 index 0000000..69054e6 --- /dev/null +++ b/docs/selfisys.sbmy_interface.html @@ -0,0 +1,456 @@ + + + + + + + + + sbmy_interface — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/selfisys.selection_functions.html b/docs/selfisys.selection_functions.html new file mode 100644 index 0000000..d3f2c1a --- /dev/null +++ b/docs/selfisys.selection_functions.html @@ -0,0 +1,465 @@ + + + + + + + + + selection_functions — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/selfisys.selfi_interface.html b/docs/selfisys.selfi_interface.html new file mode 100644 index 0000000..beca3b0 --- /dev/null +++ b/docs/selfisys.selfi_interface.html @@ -0,0 +1,343 @@ + + + + + + + + + selfi_interface — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/selfisys.setup_model.html b/docs/selfisys.setup_model.html new file mode 100644 index 0000000..0c87232 --- /dev/null +++ b/docs/selfisys.setup_model.html @@ -0,0 +1,450 @@ + + + + + + + + + setup_model — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/selfisys.utils.html b/docs/selfisys.utils.html new file mode 100644 index 0000000..04ea229 --- /dev/null +++ b/docs/selfisys.utils.html @@ -0,0 +1,1566 @@ + + + + + + + + + selfisys.utils package — SelfiSys /Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/ documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/source/CONTRIBUTING.md b/docs/source/CONTRIBUTING.md new file mode 100644 index 0000000..76f4a5c --- /dev/null +++ b/docs/source/CONTRIBUTING.md @@ -0,0 +1,58 @@ +# Contributing to SelfiSys + +We welcome contributions to the SelfiSys repository! Please follow these guidelines when contributing. + +--- + +## Reporting Issues + +If you find a bug or have a suggestion, please open an issue in the [GitHub repository](https://github.com/hoellin/selfisys_public). Include as much detail as possible: +- Steps to reproduce the issue +- Expected vs. actual behaviour +- Relevant error messages or logs (if applicable) +- Suggestions for improvement (if applicable) + +If you are unsure whether your issue is a bug or have questions about the code, you are welcome to open an issue for discussion. + +## Submitting Contributions + +1. Fork the repository and create a new branch for your changes. +2. Ensure your contributions are well-documented and adhere to the highest coding standards. +3. Test your changes thoroughly before submitting: + - Ensure Jupyter notebooks run without errors from top to bottom. + - Validate new functionality or fixes using appropriate test cases. +4. Before submitting a pull request, synchronise your fork with the main repository to incorporate upstream changes. + - Add the main repository as a remote. + ```bash + git remote add upstream https://github.com/hoellin/selfisys_public.git + ``` + - Fetch the latest changes from the upstream repository. + ```bash + git fetch upstream + ``` + - Merge the changes into your local repository. + ```bash + git merge upstream/main + ``` +5. Open a pull request describing your changes to the main repository. + +## Style Guidelines + +Follow best practices for Python coding and Jupyter notebooks. + +### Python code + +Refer to the [PEP 8 Style Guide](https://pep8.org/) for Python coding standards. + +### Jupyter Notebooks + +- Use clear and concise Markdown cells to explain code and results. +- Avoid leaving unnecessary output (e.g., debugging print statements). +- Ensure notebooks are runnable from top to bottom. +- For tips on creating clean and effective Jupyter notebooks, see [Jupyter Notebook Best Practices](https://realpython.com/jupyter-notebook-best-practices/). + +--- + +## Questions? + +If you have any questions or need further clarification, feel free to [contact the authors](mailto:tristan.hoellinger@iap.fr). \ No newline at end of file diff --git a/docs/source/README.md b/docs/source/README.md new file mode 100644 index 0000000..e85d351 --- /dev/null +++ b/docs/source/README.md @@ -0,0 +1,83 @@ +# SelfiSys: Assess the Impact of Systematic Effects in Galaxy Surveys + +[![arXiv](https://img.shields.io/badge/astro--ph.CO-arxiv%3A2412.04443-B31B1B.svg?style=flat)](https://arxiv.org/abs/2412.04443) +[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/hoellin/selfisys_public/blob/main/LICENSE) +[![GitHub version](https://img.shields.io/github/tag/hoellin/selfisys_public.svg?label=version)](https://github.com/hoellin/selfisys_public) +[![GitHub last commit](https://img.shields.io/github/last-commit/hoellin/selfisys_public.svg)](https://github.com/hoellin/selfisys_public/commits/main) + + +**SelfiSys** is a Python package designed to address the issue of model misspecification in field-based, implicit likelihood cosmological inference. + +It leverages the inferred initial matter power spectrum, enabling a thorough diagnosis of systematic effects in large-scale spectroscopic galaxy surveys. + +## Key Features + +- **Custom hidden-box forward models** + +We provide a `HiddenBox` class to simulate realistic spectroscopic galaxy surveys. It accommodates fully non-linear gravitational evolution, and incorporates multiple systematic effects observed in real-world survey, e.g., misspecified galaxy bias, survey mask, selection functions, dust extinction, line interlopers, or inaccurate gravity solver. + +- **Diagnosis of systematic effects** + +Diagnose the impact of systematic effects using the inferred initial matter power spectrum, prior to performing cosmological inference. + +- **Cosmological inference** + +Perform inference of cosmological parameters using Approximate Bayesian Computation (ABC) with a Population Monte Carlo (PMC) sampler. + +--- + +## Documentation + +The documentation, including a detailed API reference, is available at [hoellin.github.io/selfisys_public](https://hoellin.github.io/selfisys_public/). + +For practical examples demonstrating how to use SelfiSys, visit the [SelfiSys Examples Repository](https://github.com/hoellin/selfisys_examples). + +## Contributors + +- **Tristan Hoellinger**, [tristan.hoellinger@iap.fr](mailto:tristan.hoellinger@iap.fr) + Principal developer and maintainer, Institut d’Astrophysique de Paris (IAP). + +For information on contributing, refer to [CONTRIBUTING.md](CONTRIBUTING.md). + +## References + +If you use the SelfiSys package in your research, please cite the following paper and feel free to [contact the authors](mailto:tristan.hoellinger@iap.fr) for feedback, collaboration opportunities, or other inquiries. + +**Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum** +*Hoellinger, T. and Leclercq, F., 2024* +[arXiv:2412.04443](https://arxiv.org/abs/2412.04443) [[astro-ph.CO]](https://arxiv.org/abs/2412.04443) [[ADS]](https://ui.adsabs.harvard.edu/abs/arXiv:2412.04443) [[pdf]](https://arxiv.org/pdf/2412.04443) + +BibTeX entry for citation: +```bibtex +@ARTICLE{hoellinger2024diagnosing, + author = {Hoellinger, Tristan and Leclercq, Florent}, + title = "{Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum}", + journal = {arXiv e-prints}, + keywords = {Astrophysics - Cosmology and Nongalactic Astrophysics, Astrophysics - Instrumentation and Methods for Astrophysics}, + year = 2024, + month = dec, + eid = {arXiv:2412.04443}, + pages = {arXiv:2412.04443}, + doi = {10.48550/arXiv.2412.04443}, +archivePrefix = {arXiv}, + eprint = {2412.04443}, +primaryClass = {astro-ph.CO}, + adsurl = {https://ui.adsabs.harvard.edu/abs/2024arXiv241204443H}, + adsnote = {Provided by the SAO/NASA Astrophysics Data System} +} +``` + +## Requirements + +The code is written in Python 3.10 and depends on the following packages: +- [`pySELFI`](https://pyselfi.readthedocs.io/en/latest/): Python implementation of the Simulator Expansion for Likelihood-Free Inference. +- [`Simbelmynë`](https://simbelmyne.readthedocs.io/en/latest/): A hierarchical probabilistic simulator for generating synthetic galaxy survey data. +- [`ELFI`](https://elfi.readthedocs.io/en/latest/): A statistical software package for likelihood-free inference, implementing in particular Approximate Bayesian Computation (ABC) with a Population Monte Carlo (PMC) sampler. + +A comprehensive list of dependencies, including version specifications to ensure reproducibility, will be provided in a yaml file, along with installation instructions, in a future release. + +--- + +## License + +This software is distributed under the GPLv3 Licence. Please review the [LICENSE](https://github.com/hoellin/selfisys_public/blob/main/LICENSE) file in the repository to understand the terms of use and ensure compliance. By downloading and using this software, you agree to the terms of the licence. \ No newline at end of file diff --git a/docs/source/REFERENCES.md b/docs/source/REFERENCES.md new file mode 100644 index 0000000..d211609 --- /dev/null +++ b/docs/source/REFERENCES.md @@ -0,0 +1,44 @@ +# References + +If you use the SelfiSys package in your research, please cite the following paper and feel free to [contact the authors](mailto:tristan.hoellinger@iap.fr) for feedback, collaboration opportunities, or other inquiries. + +**Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum** +*Hoellinger, T. and Leclercq, F., 2024* +[arXiv:2412.04443](https://arxiv.org/abs/2412.04443) [[astro-ph.CO]](https://arxiv.org/abs/2412.04443) [[ADS]](https://ui.adsabs.harvard.edu/abs/arXiv:2412.04443) [[pdf]](https://arxiv.org/pdf/2412.04443) + +BibTeX entry for citation: +```bibtex +@ARTICLE{hoellinger2024diagnosing, + author = {Hoellinger, Tristan and Leclercq, Florent}, + title = "{Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum}", + journal = {arXiv e-prints}, + keywords = {Astrophysics - Cosmology and Nongalactic Astrophysics, Astrophysics - Instrumentation and Methods for Astrophysics}, + year = 2024, + month = dec, + eid = {arXiv:2412.04443}, + pages = {arXiv:2412.04443}, + doi = {10.48550/arXiv.2412.04443}, +archivePrefix = {arXiv}, + eprint = {2412.04443}, +primaryClass = {astro-ph.CO}, + adsurl = {https://ui.adsabs.harvard.edu/abs/2024arXiv241204443H}, + adsnote = {Provided by the SAO/NASA Astrophysics Data System} +} +``` + +Other references relevant to the SelfiSys pipeline and cited in the code are listed below. + +- **[jasche2010bayesian]** +Jasche, J., Kitaura, F. S., Wandelt, B. D., and Enßlin, T. A., “Bayesian power-spectrum inference for large-scale structure data”, Monthly Notices of the Royal Astronomical Society, vol. 406, no. 1, OUP, pp. 60–85, 2010. [doi:10.1111/j.1365-2966.2010.16610.x](https://doi.org/10.48550/arXiv.0911.2493). + +- **[gil2015power]** +Gil-Marín, H., “The power spectrum and bispectrum of SDSS DR11 BOSS galaxies - I. Bias and gravity”, Monthly Notices of the Royal Astronomical Society, vol. 451, no. 1, OUP, pp. 539–580, 2015. [doi:10.1093/mnras/stv961](https://doi.org/10.48550/arXiv.1407.5668). + +- **[howlett2015clustering]** +Howlett, C., Ross, A. J., Samushia, L., Percival, W. J., and Manera, M., “The clustering of the SDSS main galaxy sample - II. Mock galaxy catalogues and a measurement of the growth of structure from redshift space distortions at z = 0.15”, Monthly Notices of the Royal Astronomical Society, vol. 449, no. 1, OUP, pp. 848–866, 2015. [doi:10.1093/mnras/stu2693](https://doi.org/10.48550/arXiv.1409.3238). + +- **[leclercq2019primordial]** +Florent Leclercq, Wolfgang Enzi, Jens Jasche, Alan Heavens, Primordial power spectrum and cosmology from black-box galaxy surveys, Monthly Notices of the Royal Astronomical Society, Volume 490, Issue 3, December 2019, Pages 4237–4253, [doi:10.1093/mnras/stz2718](https://doi.org/10.1093/mnras/stz2718). + +- **[hoellinger2024diagnosing]** +Hoellinger, T. and Leclercq, F., “Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum”, arXiv e-prints, Art. no. arXiv:2412.04443, 2024. [doi.org:10.48550/arXiv.2412.04443](https://doi.org/10.48550/arXiv.2412.04443). \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..9fdcf3a --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,114 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "SelfiSys" +copyright = "2025, Tristan Hoellinger" +author = "Tristan Hoellinger" +release = ( + "/Users/hoellinger/Library/CloudStorage/Dropbox/travail/these/science/code/SELFI/selfisys/src/" +) + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx.ext.autodoc", # Automatically generate documentation from docstrings. + "sphinx.ext.viewcode", # Adds links to highlighted source code. + "sphinx.ext.napoleon", # Supports Google and NumPy-style docstrings. + "myst_parser", # Markdown support. + "sphinx.ext.autosummary", # Generate summary tables for API docs. + "sphinx.ext.mathjax", # For rendering mathematical equations using MathJax. + "sphinx.ext.todo", # Support for TODO notes. +] + +templates_path = ["_templates"] +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] +html_extra_path = ["_static"] + +html_theme_options = { + "collapse_navigation": False, + "sticky_navigation": True, + "navigation_depth": 4, + "style_external_links": True, + "titles_only": False, +} + +# Add GitHub link +html_context = { + "display_github": True, # Enables the link + "github_user": "hoellin", # GitHub username + "github_repo": "selfisys_public", # Repository name + "github_version": "main", # Branch name + "conf_py_path": "/docs/source/", # Path to the documentation directory in the repo +} + +import os +import sys +import shutil + +# Add the project's source path to sys.path +sys.path.insert(0, os.path.abspath("../../src")) + +# Copy Markdown files from parent directory +markdown_files = ["README.md", "CONTRIBUTING.md", "REFERENCES.md"] +for md_file in markdown_files: + src = os.path.abspath(f"../../{md_file}") # Adjust the path if necessary + dst = os.path.join(os.path.dirname(__file__), os.path.basename(md_file)) + try: + if os.path.exists(src) and not os.path.exists(dst): + shutil.copy(src, dst) + print(f"Copied {src} to {dst}") + except Exception as e: + print(f"Error copying {md_file}: {e}") + + +def generate_rst_files(): + """ + Automatically generate .rst files for standalone Python modules. + Avoids regeneration if files already exist. + """ + source_dir = os.path.abspath("src/selfisys") # Adjust to your source directory + output_dir = os.path.join(os.path.dirname(__file__)) # docs/source directory + + # Ensure source_dir exists + if not os.path.isdir(source_dir): + raise FileNotFoundError(f"The source directory '{source_dir}' does not exist.") + + # List all Python files in the source directory (excluding __init__.py) + for filename in os.listdir(source_dir): + if filename.endswith(".py") and filename != "__init__.py": + module_name = filename[:-3] # Remove .py extension + output_file = os.path.join(output_dir, f"selfisys.{module_name}.rst") + + # Skip regeneration if the file already exists + if os.path.exists(output_file): + continue + + # Write the .rst file content + with open(output_file, "w") as f: + # Module header + f.write(f"{module_name}\n") + f.write("=" * len(module_name) + "\n\n") + f.write(f".. automodule:: selfisys.{module_name}\n") + f.write(" :members:\n") + f.write(" :undoc-members:\n") + f.write(" :show-inheritance:\n") + + print(f"Generated {output_file}") + + +# Register a Sphinx hook to generate .rst files before the build +def setup(app): + app.connect("builder-inited", lambda app: generate_rst_files()) diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..26dda27 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,95 @@ +SelfiSys: Assess the Impact of Systematic Effects in Galaxy Surveys +=================================================================== + +.. image:: https://img.shields.io/badge/astro--ph.CO-arxiv%3A2412.04443-B31B1B.svg + :target: https://arxiv.org/abs/2412.04443 + :alt: arXiv + +.. image:: https://img.shields.io/github/v/tag/hoellin/selfisys_public.svg?label=version + :target: https://github.com/hoellin/selfisys_public/releases + :alt: GitHub Release + +.. image:: https://img.shields.io/github/last-commit/hoellin/selfisys_public + :target: https://github.com/hoellin/selfisys_public/commits/main + :alt: Last Commit + +.. image:: https://img.shields.io/badge/License-GPLv3-blue.svg + :target: https://github.com/hoellin/selfisys_public/blob/main/LICENSE + :alt: License + +**SelfiSys** is a Python package designed to address the issue of model misspecification in field-based, implicit likelihood cosmological inference. + +It leverages the inferred initial matter power spectrum, enabling a thorough diagnosis of systematic effects in large-scale spectroscopic galaxy surveys. + +Key Features +------------ + +- **Custom hidden-box forward models** + We provide a `HiddenBox` class to simulate realistic spectroscopic galaxy surveys. It accommodates fully non-linear gravitational evolution, and incorporates multiple systematic effects observed in real-world survey, e.g., misspecified galaxy bias, survey mask, selection functions, dust extinction, line interlopers, or inaccurate gravity solver. +- **Diagnosis of systematic effects** + Diagnose the impact of systematic effects using the inferred initial matter power spectrum, prior to performing cosmological inference. +- **Cosmological inference** + Perform inference of cosmological parameters using Approximate Bayesian Computation (ABC) with a Population Monte Carlo (PMC) sampler. + +For practical examples demonstrating how to use SelfiSys, visit the `SelfiSys Examples Repository `_. + +References +---------- + +If you use the SelfiSys package in your research, please cite the following paper and feel free to `contact the authors `_ for feedback, collaboration opportunities, or other inquiries. + +**Diagnosing Systematic Effects Using the Inferred Initial Power Spectrum** +*Hoellinger, T. and Leclercq, F., arXiv e-prints*, 2024 +`arXiv:2412.04443 `_ +`[astro-ph.CO] `_ +`[ADS] `_ +`[pdf] `_ + +Contributors +------------ + +- **Tristan Hoellinger** + `tristan.hoellinger@iap.fr `_ + + Principal developer and maintainer, Institut d’Astrophysique de Paris (IAP). + +License +------- + +This software is distributed under the GPLv3 Licence. Please review the `LICENSE `_ file in the repository to understand the terms of use and ensure compliance. By downloading and using this software, you agree to the terms of the licence. + +Requirements +------------ + +The code is written in Python 3.10 and depends on the following packages: + +- `pySELFI `_: Python implementation of the Simulator Expansion for Likelihood-Free Inference. +- `Simbelmynë `_: A hierarchical probabilistic simulator for generating synthetic galaxy survey data. +- `ELFI `_: A statistical software package for likelihood-free inference, implementing Approximate Bayesian Computation (ABC) with a Population Monte Carlo (PMC) sampler. + +A comprehensive list of dependencies, along with installation instructions, will be provided in a future release. + +.. toctree:: + :maxdepth: 2 + :caption: API Documentation + + selfisys.hiddenbox + selfisys.normalise_hb + selfisys.prior + selfisys.selection_functions + selfisys.selfi_interface + selfisys.sbmy_interface + selfisys.grf + selfisys.utils + +.. toctree:: + :maxdepth: 2 + :caption: Contribute + + ../../CONTRIBUTING.md + +.. toctree:: + :maxdepth: 2 + :caption: References + + ../../REFERENCES.md \ No newline at end of file diff --git a/docs/source/selfisys.global_parameters.rst b/docs/source/selfisys.global_parameters.rst new file mode 100644 index 0000000..0efb881 --- /dev/null +++ b/docs/source/selfisys.global_parameters.rst @@ -0,0 +1,7 @@ +global_parameters +================= + +.. automodule:: selfisys.global_parameters + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/selfisys.grf.rst b/docs/source/selfisys.grf.rst new file mode 100644 index 0000000..d10cbf8 --- /dev/null +++ b/docs/source/selfisys.grf.rst @@ -0,0 +1,7 @@ +grf +=== + +.. automodule:: selfisys.grf + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/selfisys.hiddenbox.rst b/docs/source/selfisys.hiddenbox.rst new file mode 100644 index 0000000..292952c --- /dev/null +++ b/docs/source/selfisys.hiddenbox.rst @@ -0,0 +1,7 @@ +hiddenbox +========= + +.. automodule:: selfisys.hiddenbox + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/selfisys.normalise_hb.rst b/docs/source/selfisys.normalise_hb.rst new file mode 100644 index 0000000..108af97 --- /dev/null +++ b/docs/source/selfisys.normalise_hb.rst @@ -0,0 +1,7 @@ +normalise_hb +============ + +.. automodule:: selfisys.normalise_hb + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/selfisys.prior.rst b/docs/source/selfisys.prior.rst new file mode 100644 index 0000000..30a73e2 --- /dev/null +++ b/docs/source/selfisys.prior.rst @@ -0,0 +1,7 @@ +prior +===== + +.. automodule:: selfisys.prior + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/selfisys.rst b/docs/source/selfisys.rst new file mode 100644 index 0000000..1863c5a --- /dev/null +++ b/docs/source/selfisys.rst @@ -0,0 +1,93 @@ +selfisys package +================ + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + selfisys.utils + +Submodules +---------- + +selfisys.global\_parameters module +---------------------------------- + +.. automodule:: selfisys.global_parameters + :members: + :undoc-members: + :show-inheritance: + +selfisys.grf module +------------------- + +.. automodule:: selfisys.grf + :members: + :undoc-members: + :show-inheritance: + +selfisys.hiddenbox module +------------------------- + +.. automodule:: selfisys.hiddenbox + :members: + :undoc-members: + :show-inheritance: + +selfisys.normalise\_hb module +----------------------------- + +.. automodule:: selfisys.normalise_hb + :members: + :undoc-members: + :show-inheritance: + +selfisys.prior module +--------------------- + +.. automodule:: selfisys.prior + :members: + :undoc-members: + :show-inheritance: + +selfisys.sbmy\_interface module +------------------------------- + +.. automodule:: selfisys.sbmy_interface + :members: + :undoc-members: + :show-inheritance: + +selfisys.selection\_functions module +------------------------------------ + +.. automodule:: selfisys.selection_functions + :members: + :undoc-members: + :show-inheritance: + +selfisys.selfi\_interface module +-------------------------------- + +.. automodule:: selfisys.selfi_interface + :members: + :undoc-members: + :show-inheritance: + +selfisys.setup\_model module +---------------------------- + +.. automodule:: selfisys.setup_model + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: selfisys + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/selfisys.sbmy_interface.rst b/docs/source/selfisys.sbmy_interface.rst new file mode 100644 index 0000000..7f1f003 --- /dev/null +++ b/docs/source/selfisys.sbmy_interface.rst @@ -0,0 +1,7 @@ +sbmy_interface +============== + +.. automodule:: selfisys.sbmy_interface + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/selfisys.selection_functions.rst b/docs/source/selfisys.selection_functions.rst new file mode 100644 index 0000000..fdb56b4 --- /dev/null +++ b/docs/source/selfisys.selection_functions.rst @@ -0,0 +1,7 @@ +selection_functions +=================== + +.. automodule:: selfisys.selection_functions + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/selfisys.selfi_interface.rst b/docs/source/selfisys.selfi_interface.rst new file mode 100644 index 0000000..32b1adf --- /dev/null +++ b/docs/source/selfisys.selfi_interface.rst @@ -0,0 +1,7 @@ +selfi_interface +=============== + +.. automodule:: selfisys.selfi_interface + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/selfisys.setup_model.rst b/docs/source/selfisys.setup_model.rst new file mode 100644 index 0000000..a9036a8 --- /dev/null +++ b/docs/source/selfisys.setup_model.rst @@ -0,0 +1,7 @@ +setup_model +=========== + +.. automodule:: selfisys.setup_model + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/selfisys.utils.rst b/docs/source/selfisys.utils.rst new file mode 100644 index 0000000..d5ac880 --- /dev/null +++ b/docs/source/selfisys.utils.rst @@ -0,0 +1,101 @@ +selfisys.utils package +====================== + +Submodules +---------- + +selfisys.utils.examples\_utils module +------------------------------------- + +.. automodule:: selfisys.utils.examples_utils + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.logger module +---------------------------- + +.. automodule:: selfisys.utils.logger + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.low\_level module +-------------------------------- + +.. automodule:: selfisys.utils.low_level + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.parser module +---------------------------- + +.. automodule:: selfisys.utils.parser + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.path\_utils module +--------------------------------- + +.. automodule:: selfisys.utils.path_utils + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.plot\_examples module +------------------------------------ + +.. automodule:: selfisys.utils.plot_examples + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.plot\_params module +---------------------------------- + +.. automodule:: selfisys.utils.plot_params + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.plot\_utils module +--------------------------------- + +.. automodule:: selfisys.utils.plot_utils + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.timestepping module +---------------------------------- + +.. automodule:: selfisys.utils.timestepping + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.tools module +--------------------------- + +.. automodule:: selfisys.utils.tools + :members: + :undoc-members: + :show-inheritance: + +selfisys.utils.workers module +----------------------------- + +.. automodule:: selfisys.utils.workers + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: selfisys.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/src/selfisys/__init__.py b/src/selfisys/__init__.py new file mode 100644 index 0000000..a421b31 --- /dev/null +++ b/src/selfisys/__init__.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +SelfiSys Package + +A Python package for diagnosing systematic effects in field-based, +implicit likelihood inference (ILI) of cosmological parameters from +large-scale spectroscopic galaxy surveys. The diagnostic utilises the +initial matter power spectrum inferred with pySELFI. + +Key functionalities: +- Setup custom models of realistic spectroscopic galaxy surveys, +- Diagnosis of systematic effects model using the initial matter power +spectrum inferred with pySELFI (https://pyselfi.readthedocs.io/), +- Perform inference of cosmological parameters using Approximate +Bayesian Computation (ABC) with a Population Monte Carlo (PMC) sampler +using ELFI (https://elfi.readthedocs.io/). +""" + +from .global_parameters import * diff --git a/src/selfisys/global_parameters.py b/src/selfisys/global_parameters.py new file mode 100644 index 0000000..011d6cd --- /dev/null +++ b/src/selfisys/global_parameters.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +"""Global parameters for this project.""" + +import os +from pathlib import Path +import numpy as np + +WHICH_SPECTRUM = "class" # available options are "eh" and "class" + +# Load global paths from environment variables +ROOT_PATH = os.getenv("SELFISYS_ROOT_PATH") +if ROOT_PATH is None: + raise EnvironmentError("Please set the 'SELFISYS_ROOT_PATH' environment variable.") +OUTPUT_PATH = os.getenv("SELFISYS_OUTPUT_PATH") +if OUTPUT_PATH is None: + raise EnvironmentError("Please set the 'SELFISYS_OUTPUT_PATH' environment variable.") + +# Default verbose level +# 0: errors only, 1: info, 2: warnings+, 3: all diagnostics, 4+: debug +DEFAULT_VERBOSE_LEVEL = 2 + +# Baseline seeds for reproducibility +BASELINE_SEEDNORM = 100030898 +BASELINE_SEEDNOISE = 200030898 +BASELINE_SEEDPHASE = 300030898 +SEEDPHASE_OBS = 100030896 +SEEDNOISE_OBS = 100030897 + +# Fiducial cosmological parameters +h_planck = 0.6766 +Omega_b_planck = 0.02242 / h_planck**2 +Omega_m_planck = 0.3111 +nS_planck = 0.9665 +sigma8_planck = 0.8102 + +planck_mean = np.array([h_planck, Omega_b_planck, Omega_m_planck, nS_planck, sigma8_planck]) +planck_cov = np.diag(np.array([0.0042, 0.00030, 0.0056, 0.0038, 0.0060]) ** 2) + +# Mock unknown ground truth parameters for consistency checks +h_obs = 0.679187146124996 +Omega_b_obs = 0.0487023481098232 +Omega_m_obs = 0.3053714257403574 +nS_obs = 0.9638467785003454 +sigma8_obs = 0.8210464735135183 + +omegas_gt = np.array([h_obs, Omega_b_obs, Omega_m_obs, nS_obs, sigma8_obs]) + +# Mapping from cosmological parameter names to corresponding indices +cosmo_params_names = [r"$h$", r"$\Omega_b$", r"$\Omega_m$", r"$n_S$", r"$\sigma_8$"] +cosmo_params_name_to_idx = {"h": 0, "Omega_b": 1, "Omega_m": 2, "n_s": 3, "sigma8": 4} + +# Minimum k value used in the normalisation of the summaries +MIN_K_NORMALISATION = 4e-2 + +params_planck_kmax_missing = { + "h": h_planck, + "Omega_r": 0.0, + "Omega_q": 1.0 - Omega_m_planck, + "Omega_b": Omega_b_planck, + "Omega_m": Omega_m_planck, + "m_ncdm": 0.0, + "Omega_k": 0.0, + "tau_reio": 0.066, + "n_s": nS_planck, + "sigma8": sigma8_planck, + "w0_fld": -1.0, + "wa_fld": 0.0, + "WhichSpectrum": WHICH_SPECTRUM, +} + +params_BBKS_kmax_missing = { + "h": h_planck, + "Omega_r": 0.0, + "Omega_q": 1.0 - Omega_m_planck, + "Omega_b": Omega_b_planck, + "Omega_m": Omega_m_planck, + "m_ncdm": 0.0, + "Omega_k": 0.0, + "tau_reio": 0.066, + "n_s": nS_planck, + "sigma8": sigma8_planck, + "w0_fld": -1.0, + "wa_fld": 0.0, + "WhichSpectrum": "BBKS", +} + +params_cosmo_obs_kmax_missing = { + "h": h_obs, + "Omega_r": 0.0, + "Omega_q": 1.0 - Omega_m_obs, + "Omega_b": Omega_b_obs, + "Omega_m": Omega_m_obs, + "m_ncdm": 0.0, + "Omega_k": 0.0, + "tau_reio": 0.066, + "n_s": nS_obs, + "sigma8": sigma8_obs, + "w0_fld": -1.0, + "wa_fld": 0.0, + "WhichSpectrum": WHICH_SPECTRUM, +} + +# Default hyperparameters for the wiggle-less prior from [leclercq2019primordial]. +THETA_NORM_GUESS = 0.05 +K_CORR_GUESS = 0.01 + +# Base ID for the observations +BASEID_OBS = "obs" diff --git a/src/selfisys/grf.py b/src/selfisys/grf.py new file mode 100644 index 0000000..0e18a2c --- /dev/null +++ b/src/selfisys/grf.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +"""Tools for generating Gaussian random fields from given power spectra. +""" + +from selfisys.utils.logger import getCustomLogger + +logger = getCustomLogger(__name__) + + +def primordial_grf( + L, + N, + seedphases, + fname_powerspectrum, + fname_outputinitialdensity, + force_sim=False, + return_g=False, + verbose=0, +): + """ + Generate a Gaussian random field from a specified input power + spectrum. + + Parameters + ---------- + L : float + Side length of the simulation box in Mpc/h. + N : int + Grid resolution (number of cells per dimension). + seedphases : int + Seed for random phase generation (for reproducibility). + fname_powerspectrum : str + File path to the input power spectrum. + fname_outputinitialdensity : str + File path to store the generated initial density field. + force_sim : bool, optional + If True, regenerate the GRF even if the output file exists. + Default is False. + return_g : bool, optional + If True, return the GRF as a numpy array. Default is False. + verbose : int, optional + Verbosity level (0 = silent, 1 = progress, 2 = detailed). + Default is 0. + + Raises + ------ + OSError + If the power spectrum file cannot be read. + RuntimeError + If an unexpected error occurs during power spectrum reading. + + Returns + ------- + numpy.ndarray or None + The GRF data if `return_g` is True, otherwise None. + """ + from os.path import exists + from gc import collect + from pysbmy.power import PowerSpectrum + from pysbmy.field import Field + + # Skip simulation if output already exists and overwrite is not requested + if not force_sim and exists(fname_outputinitialdensity): + from pysbmy.field import read_basefield + + if verbose > 0: + logger.info(f"{fname_outputinitialdensity} already exists. Skipping simulation.") + return read_basefield(fname_outputinitialdensity).data if return_g else None + + # Read the power spectrum + try: + P = PowerSpectrum.read(fname_powerspectrum) + except OSError as e: + logger.error(f"Unable to read power spectrum file: {fname_powerspectrum}") + raise + except Exception as e: + logger.exception(f"Unexpected error while reading power spectrum: {e}") + raise + + # Generate the Gaussian random field + if verbose > 1: + g = Field.GRF(L, L, L, 0, 0, 0, N, N, N, P, 1e3, seedphases) # a_init = 1e3 + else: + from selfisys.utils.low_level import stdout_redirector + from io import BytesIO + + # Suppress standard output to avoid cluttering logs + with BytesIO() as f: + with stdout_redirector(f): + g = Field.GRF(L, L, L, 0, 0, 0, N, N, N, P, 1e3, seedphases) + + # Write the field to disk + g.write(fname_outputinitialdensity) + field = g.data.copy() if return_g else None + + # Free memory + del g + collect() + + return field diff --git a/src/selfisys/hiddenbox.py b/src/selfisys/hiddenbox.py new file mode 100644 index 0000000..7b0d83e --- /dev/null +++ b/src/selfisys/hiddenbox.py @@ -0,0 +1,1513 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +"""This module provides the HiddenBox class, which is used to define +standalone stochastic forward models of large-scale spectroscopic +galaxy surveys. + +The HiddenBox class is compatible with the pySELFI package. +""" + +import os +from gc import collect +from typing import Callable, Any, List, Optional, Union + +import h5py +import numpy as np +from selfisys.global_parameters import DEFAULT_VERBOSE_LEVEL +from selfisys.utils.parser import joinstrs_only + + +class HiddenBox: + """This class represents custom forward data model of large-scale + spectroscopic galaxy surveys.""" + + def __init__( + self, + k_s: Any, + P_ss_path: str, + Pbins_bnd: Any, + theta2P: Callable, + P: int, + size: int, + L: float, + G_sim_path: str, + G_ss_path: str, + Np0: int, + Npm0: int, + fsimdir: str, + noise_std: Optional[float] = None, + radial_selection: Optional[str] = None, + selection_params: Optional[List[Any]] = None, + observed_density: Optional[float] = None, + linear_bias: Optional[Union[float, List[float]]] = None, + norm_csts: Optional[Union[float, List[float]]] = None, + survey_mask_path: Optional[str] = None, + local_mask_prefix: Optional[str] = None, + sim_params: Optional[str] = None, + eff_redshifts: Optional[List[float]] = None, + TimeSteps: Optional[List[int]] = None, + TimeStepDistribution: Optional[str] = None, + seedphase: Optional[int] = None, + seednoise: Optional[int] = None, + fixnoise: bool = False, + seednorm: Optional[int] = None, + save_frequency: Optional[int] = None, + reset: bool = False, + verbosity: int = DEFAULT_VERBOSE_LEVEL, + **kwargs, + ): + """ + Initialise the HiddenBox. + + Parameters + ---------- + k_s : array-like + Vector of input support wavenumbers. + P_ss_path : str + Path to the spectrum used to normalise the outputs. + Pbins_bnd : array-like + Vector of bin boundaries for the summary statistics. + theta2P : Callable + Function to convert theta vectors to initial power spectra. + P : int + Dimension of the output summary statistics. + size : int + Side length of the simulation box in voxels. + L : float + Side length of the simulation box in Mpc/h. + G_sim_path : str + Path to the simulation grid. + G_ss_path : str + Path to the summary grid. + Np0 : int + Number of dark matter particles per spatial dimension. + Npm0 : int + Side length of the particle-mesh grid in voxels. + fsimdir : str + Output directory. + noise_std : float, optional + Standard deviation for the Gaussian noise. Default is + `None`. + radial_selection : str, optional + Type of radial selection mask. Default is `None`, + corresponding to no radial selection. + + Available options: + - 'multiple_lognormal': Use multiple log-normal radial + selection functions. + selection_params : list, optional + List of parameters for the radial selection mask, for each + population. Default is `None`. + + For the 'multiple_lognormal' radial selection, + `selection_params` is a shape `(3, N_pop)` array comprising + the three following parameters for each population: + - selection_std : (float) + Standard deviation of the distribution, e.g., + constant × (1 + z). + - selection_mean : (float) + Mean of the distribution in Gpc/h. + - selection_rescale : (float, optional) + Individually rescale the distributions by the given + value. If `None`, the global maximum is normalised to + `1`. + observed_density : float, optional + Mean galaxy density. Default is `None`. + linear_bias : float or list of float, optional + First-order linear galaxy biases. If `None`, use the dark + matter density to compute the summaries. Default is `None`. + norm_csts : float or list of float, optional + If not `None`, normalise the output of the hidden box + accordingly. + + For `radial_selection == 'multiple_lognormal'`, `norm_csts` + must be a list of `N_pop` values. Default is `None`. + survey_mask_path : str, optional + If not `None`, apply the corresponding survey mask to the + observed field. Default is `None`. + local_mask_prefix : str, optional + Prefix for the local copy of the survey mask. If `None`, use + the default name. Default is `None`. + sim_params : str, optional + Set of simulation parameters to be used for Simbelmynë. + Check `setup_sbmy_parfiles` in `selfisys.sbmy_parser` for + details. Default is `None`. + eff_redshifts : list, optional + Effective redshifts for the time steps. Default is `None`. + TimeSteps : list, optional + Number of time steps to reach the corresponding effective + redshifts. Default is `None`. + TimeStepDistribution : str, optional + Path to the Simbelmynë time step distribution file. Default + is `None`. + seedphase : int, optional + Seed to generate the initial white noise realisation. + Default is `None`. + seednoise : int, optional + Initial state of the RNG to generate the noise sequence. If + `fixnoise` is True, the seed used in `compute_pool` is + always `seednoise`. If `fixnoise` is False, it is + `seednoise + i` for realisation `i`. Default is `None`. + fixnoise : bool, optional + Whether to fix the noise realisation. If `True`, always use + `seednoise`. Default is `False`. + seednorm : int, optional + Seed used for normalisation. Default is `None`. + save_frequency : int, optional + Save the outputs of the hidden box to disk every + `save_frequency` evaluations. Default is `None`. + reset : bool, optional + Whether to always force reset the survey box when the hidden + `HiddenBox` object is instantiated. Default is `False`. + verbosity : int, optional + Verbosity level of the hidden box. `0` is silent, `1` is + minimal, `2` is verbose. Default is `DEFAULT_VERBOSE_LEVEL`. + **kwargs + Additional optional keyword arguments. + """ + # Mandatory attributes + self.k_s = k_s + self.P_ss_path = P_ss_path + self.Pbins_bnd = Pbins_bnd + self.theta2P = theta2P + self.P = P + self.size = size + self.L = L + self.G_sim_path = G_sim_path + self.G_ss_path = G_ss_path + self.Np0 = Np0 + self.Npm0 = Npm0 + self.fsimdir = fsimdir + + # Optional attributes + self.noise_std = noise_std + self.radial_selection = radial_selection + self.selection_params = selection_params + self.observed_density = observed_density + self.linear_bias = linear_bias + self.norm_csts = norm_csts + self.survey_mask_path = survey_mask_path + self.local_mask_prefix = local_mask_prefix + self.sim_params = sim_params + self.eff_redshifts = eff_redshifts + self.TimeSteps = TimeSteps + self.TimeStepDistribution = TimeStepDistribution + self.seedphase = seedphase + self.seednoise = seednoise + self.fixnoise = fixnoise + self.seednorm = seednorm + self.save_frequency = save_frequency + self.reset = reset + self.verbosity = verbosity + + # Additional attributes from kwargs + for key, value in kwargs.items(): + setattr(self, key, value) + + # Compute default values + self._set_defaults() + + # Create the window function W(n, r) = C(n) * R(r) + self._init_survey_mask() + self._init_radial_selection() + + def reset_survey(self): + """Re-initialise the survey mask C(n) and radial selection + function R(r). + """ + self._init_survey_mask(reset=True) + self._init_radial_selection(reset=True) + + def update(self, **kwargs): + """Updates the given parameter(s) of the hidden box with the + given value(s). + + Parameters + ---------- + **kwargs : dict + dictionary of parameters to update + + """ + for key, value in kwargs.items(): + setattr(self, key, value) + + def make_data( + self, + cosmo, + id, + seedphase, + seednoise, + force_powerspectrum=False, + force_parfiles=False, + force_sim=False, + force_cosmo=False, + force_phase=False, + d=-1, + remove_sbmy=True, + verbosity=None, + return_g=False, + RSDs=True, + prefix_mocks=None, + ): + """Generate simulated data based on the given cosmological + parameters. + + Parameters + ---------- + cosmo : dict + Cosmological and infrastructure parameters. + id : int or str, optional + Identifier used as a suffix in the file names. Default is + `0`. + seedphase : int or list of int + Seed to generate the initial white noise in Fourier space. + seednoise : int or list of int + Seed to generate the noise realisation. + force_powerspectrum : bool, optional + Force recomputing the input spectrum. Default is `False`. + force_parfiles : bool, optional + Force recomputing the parameter files. Default is `False`. + force_sim : bool, optional + Force recomputing the simulation. Default is `False`. + force_cosmo : bool, optional + Force recomputing the cosmological parameters. Default is + `False`. + force_phase : bool, optional + Force recomputing the initial phase. Default is `False`. + d : int, optional + Direction in the parameter space. Default is `-1`. + remove_sbmy : bool, optional + Whether to remove most Simbelmynë output files after use for + disk space management. Default is `True`. + verbosity : int, optional + Verbosity level. If `None`, use self.verbosity. Default is + `None`. + return_g : bool, optional + Whether to return the full field alongside the summary. + Default is `False`. + RSDs : bool, optional + Whether to compute the redshift-space distortions. Default + is `True`. + prefix_mocks : str, optional + Prefix for the mock data. If None, use self.prefix_mocks. + Default is `None`. + + Returns + ------- + Phi : ndarray + Vector of summary statistics. + g : ndarray or list of ndarray or None + Observed field(s) if return_g is `True`; otherwise `None`. + """ + from selfisys.utils.path_utils import get_file_names + from selfisys.sbmy_interface import get_power_spectrum_from_cosmo + + self._PrintMessage(0, f"Making mock data...", verbosity) + self._indent() + + names = get_file_names( + self.fsimdir, + id, + self.sim_params, + self.TimeSteps, + prefix_mocks or self.prefix_mocks, + self.gravity_on, + return_g, + ) + + # Save cosmological parameters + self._save_cosmo(cosmo, names["fname_cosmo"], force_cosmo) + + # Generate the input initial matter power spectrum + self._PrintMessage(1, f"Computing initial power spectrum...", verbosity) + get_power_spectrum_from_cosmo( + self.L, self.size, cosmo, names["fname_power_spectrum"], force_powerspectrum + ) + self._PrintMessage(1, f"Computing initial power spectrum done.", verbosity) + + # Generate the simulated galaxy survey data + self._PrintMessage(1, f"Running the forward model...", verbosity) + Phi, g = self._aux_hiddenbox( + d, + seedphase, + seednoise, + names, + force_parfiles=force_parfiles, + force_sim=force_sim, + force_phase=force_phase, + return_g=return_g, + RSDs=RSDs, + ) + self._PrintMessage(1, f"Running the forward model done.", verbosity) + + if remove_sbmy and self.gravity_on: + self._clean_output(names["fname_outputinitialdensity"]) + + self._unindent() + self._PrintMessage(1, f"Making mock data done.", verbosity) + + return (Phi, g) if return_g else Phi + + def evaluate( + self, + theta, + d, + seedphase, + seednoise, + i=0, + N=0, + force_powerspectrum=False, + force_parfiles=False, + force_sim=False, + remove_sbmy=False, + theta_is_p=False, + simspath=None, + check_output=False, + RSDs=True, + abc=False, + cosmo_vect=None, + ): + """Evaluate the hidden box for a given input power spectrum. + + The result is deterministic (the phase is fixed), except as it + is modified by nuisance parameters if any. + + This routine is used by `pySELFI` to compute the gradient of the + hidden box with respect to the power spectrum, and by ABC-PMC to + evaluate the forward model. + + Parameters + ---------- + theta : ndarray + Power spectrum values at the support wavenumbers. + d : int + Direction in parameter space, from `0` to `S`. + seedphase : int or list of int + Seed to generate the initial white noise in Fourier space. + seednoise : int or list of int + Seed to generate the noise realisation. + i : int, optional + Current evaluation index of the hidden box. Default is `0`. + N : int, optional + Total number of evaluations of the hidden box. Default is + `0`. + force_powerspectrum : bool, optional + If `True`, force recomputation of the power spectrum at the + values of the Fourier grid. Default is `False`. + force_parfiles : bool, optional + If `True`, overwrite existing parameter files. Default is + `False`. + force_sim : bool, optional + If `True`, rerun the simulation even if the output density + already exists. Default is `False`. + remove_sbmy : bool, optional + If `True`, remove Simbelmynë output files from disk. Default + is `False`. + theta_is_p : bool, optional + Set to `True` if `theta` is already an unnormalised power + spectrum. Default is `False`. + simspath : str, optional + Path to the simulations directory. Default is `None`. + check_output : bool, optional + If `True`, check the integrity of the output file and + recompute if corrupted. Default is `False`. + RSDs : bool, optional + Whether to compute redshift-space distortions. Default is + `True`. + abc : bool or str, optional + If not False, remove most output files after evaluation. + Default is `False`. + cosmo_vect : ndarray, optional + Cosmological parameters. Required if `abc` is `True`. + + Returns + ------- + Phi : ndarray + Vector of summary statistics. + """ + from selfisys.utils.path_utils import file_names_evaluate + + if abc and cosmo_vect is None: + raise ValueError("cosmo_vect must be provided when using ABC.") + if not abc and cosmo_vect is not None: + raise ValueError("cosmo_vect must not be provided when not using ABC.") + + if not theta_is_p: + self._PrintMessage( + 1, + f"Direction {d}. Evaluating hidden box (index {i}/{N - 1})...", + ) + self._indent() + + simdir = simspath or self.fsimdir + simdir_d = os.path.join(simdir, joinstrs_only(["pool/", abc, "d", str(d)])) + + # Get the file names for the current evaluation + names = file_names_evaluate( + simdir, + simdir_d, + d, + i, + self.sim_params, + self.TimeSteps, + self.prefix_mocks, + abc, + self.gravity_on, + ) + fname_power_spectrum = names["fname_power_spectrum"] + fname_simparfile = names["fname_simparfile"] + fname_whitenoise = names["fname_whitenoise"] + fname_outputinitialdensity = names["fname_outputinitialdensity"] + + # Interpolate the power spectrum values over the Fourier grid + self._PrintMessage(1, f"Interpolating spectrum over the Fourier grid...") + self._power_spectrum_from_theta( + theta, fname_power_spectrum, theta_is_p, force=force_powerspectrum + ) + self._PrintMessage(1, f"Interpolating spectrum over the Fourier grid done.") + + # Compute the simulated data + self._PrintMessage(1, f"Generating observed summary...") + Phi, _ = self._aux_hiddenbox( + d, + seedphase, + seednoise, + names, + force_parfiles, + force_sim=force_sim, + force_phase=False, + return_g=False, + check_output=check_output, + RSDs=RSDs, + sample=cosmo_vect, + ) + self._PrintMessage(1, f"Generating observed summary done.") + + # Clean up the output files + if remove_sbmy and self.gravity_on: + self._clean_output(fname_outputinitialdensity) + if abc: + self._clean_output(fname_power_spectrum) + self._clean_output(fname_simparfile) + self._clean_output(fname_whitenoise) + for f in os.listdir(simdir_d): + if self.TimeSteps is not None: + if f.startswith(f"output_realdensity_d{d}_p{i}_{self.TimeSteps[0]}"): + self._clean_output(os.path.join(simdir_d, f)) + else: + if f.startswith(f"output_realdensity_d{d}_p{i}"): + self._clean_output(os.path.join(simdir_d, f)) + if f.startswith(f"output_density_d{d}_p{i}"): + self._clean_output(os.path.join(simdir_d, f)) + if not self.gravity_on: + self._clean_output(fname_outputinitialdensity) + + if not theta_is_p: + self._unindent() + self._PrintMessage( + 1, + f"Direction {d}. Evaluation done (index {i}/{N - 1}).", + ) + + return Phi + + def switch_recompute_pool(self, prefix_mocks=None): + """Toggle recomputation of the pool for future `compute_pool` + calls. + + Parameters + ---------- + prefix_mocks : str, optional + Prefix for the future simulation files. Default is `None`. + """ + self._force_recompute_mocks = not self.force_recompute_mocks + self._prefix_mocks = prefix_mocks if prefix_mocks is not None else None + + def switch_setup(self): + """Toggle the setup-only mode.""" + self._setup_only = not self.setup_only + + def compute_pool( + self, + theta, + d, + pool_fname, + N, + index=None, + force_powerspectrum=False, + force_parfiles=False, + force_sim=False, + remove_sbmy=False, + theta_is_p=False, + simspath=None, + bar=False, + ): + """Compute a pool of realisations of the hidden box compatible + with `pySELFI`. + + Parameters + ---------- + theta : ndarray + Power spectrum values at the support wavenumbers. + d : int + Direction in parameter space, from 0 to S. + pool_fname : str + Filename for the pool. + N : int + Number of realisations required at the given direction. + index : int, optional + Index of a single simulation to run. Default is `None`. + force_powerspectrum : bool, optional + If True, force recomputation of the power spectrum. Default + is `False`. + force_parfiles : bool, optional + If True, overwrite existing parameter files. Default is + `False`. + force_sim : bool, optional + If True, rerun the simulation even if the output density + already exists. Default is `False`. + remove_sbmy : bool, optional + If True, remove Simbelmynë output files from disk. Default + is `False`. + theta_is_p : bool, optional + Set to True when `theta` is already an unnormalised power + spectrum. Default is `False`. + simspath : str, optional + Path indicating where to store the simulations. Default is + `None`. + bar : bool, optional + If True, display a progress bar. Default is `False`. + + Returns + ------- + p : Pool + Simulation pool object. + """ + import tqdm.auto as tqdm + from pyselfi.pool import pool as Pool + + self._PrintMessage(1, f"Computing a pool of realisations of the hidden box...") + + pool_fname = str(pool_fname) + if self.force_recompute_mocks: + if os.path.exists(pool_fname): + os.remove(pool_fname) + + p = Pool(pool_fname, N, retro=False) + ids = list(range(N)) if index is None else [index] + + def worker(i): + this_seedphase = self._get_current_seed(self.__global_seedphase, False, i) + this_seednoise = self._get_current_seed(self.__global_seednoise, self.fixnoise, i) + Phi = self.evaluate( + theta, + d, + this_seedphase, + this_seednoise, + i=i, + N=N, + force_powerspectrum=force_powerspectrum, + force_parfiles=force_parfiles, + force_sim=force_sim, + remove_sbmy=remove_sbmy, + theta_is_p=theta_is_p, + simspath=simspath, + ) + p.add_sim(Phi, i) + + iterator = tqdm.tqdm(ids, desc=f"Direction {d}/{self.S}") if bar else ids + for i in iterator: + worker(i) + + if index is None: + p.load_sims() + p.save_all() + return p + + def load_pool(self, pool_fname, N): + """Load a pool of realisations of the hidden box. + + Parameters + ---------- + pool_fname : str + Filename of the pool to load. + N : int + Number of realisations in the pool. + + Returns + ------- + p : Pool + Simulation pool object. + """ + from pyselfi.pool import pool as Pool + + pool_fname = str(pool_fname) + p = Pool(pool_fname, N, retro=False) + p.load_sims() + p.save_all() + return p + + @property + def Npop(self): + """Number of populations.""" + return self._Npop + + @Npop.setter + def Npop(self, _): + """Compute the number of populations.""" + if self.radial_selection == "multiple_lognormal": + if self.selection_params is not None: + self._Npop = len(self.selection_params[0]) + if self.linear_bias is not None and len(self.linear_bias) != self._Npop: + raise ValueError("Length of linear_bias must match the number of populations.") + if self.norm_csts is not None and len(self.norm_csts) != self._Npop: + raise ValueError("Length of norm_csts must match the number of populations.") + else: + raise ValueError( + "Selection parameters are required for multiple_lognormal radial selection." + ) + else: + self._Npop = 1 + + @property + def gravity_on(self): + """Whether gravity is enabled.""" + return self._gravity_on + + @gravity_on.setter + def gravity_on(self, _): + """Compute and set the gravity status.""" + if self.sim_params: + self._gravity_on = not self.sim_params.startswith("nograv") + else: + self._gravity_on = True + + @property + def Ntimesteps(self): + """Number of time steps.""" + return self._Ntimesteps + + @Ntimesteps.setter + def Ntimesteps(self, _): + """Compute and set the number of time steps.""" + if self.sim_params: + if self.sim_params.startswith("splitLPT"): + self._Ntimesteps = len(self.eff_redshifts) + elif self.sim_params.startswith("split"): + self._Ntimesteps = len(self.TimeStepDistribution) + elif self.sim_params.startswith("nograv"): + self._Ntimesteps = None + else: + self._Ntimesteps = None + else: + self._Ntimesteps = None + + @property + def force_recompute_mocks(self): + """Whether to force recomputation of mocks.""" + return self._force_recompute_mocks + + @property + def setup_only(self): + """Whether to only set up the hidden box.""" + return self._setup_only + + @property + def prefix_mocks(self): + """Prefix for the mocks.""" + return self._prefix_mocks + + @property + def modified_selfi(self): + """Whether to use the modified selfi.""" + return self._modified_selfi + + @property + def force_neglect_lightcone(self): + """Whether to force neglecting the lightcone.""" + return self._force_neglect_lightcone + + @property + def Psingle(self): + """Number of summary statistics for each population.""" + return self._Psingle + + @Psingle.setter + def Psingle(self, _): + """Dimension of the summary statistics for a single population, + if relevant. + """ + if self.radial_selection == "multiple_lognormal": + self._Psingle = self.P // self.Npop + else: + self._Psingle = self.P + + @property + def __global_seedphase(self): + return self.seedphase + + @property + def __global_seednoise(self): + return self.seednoise + + @property + def __global_seednorm(self): + """Global seed for the normalisation constants.""" + return self.seednorm + + def _set_defaults(self): + """Set default values and ensure consistency of the hidden box + configuration. + """ + if not hasattr(self, "modeldir"): + self.modeldir = os.path.join(self.fsimdir, "model") + + self._force_recompute_mocks = False + self._setup_only = False + self._prefix_mocks = None + self._modified_selfi = True + self._force_neglect_lightcone = False + + self.S = len(self.k_s) + # The following attributes are set by the setters + self.Ntimesteps = None + self.gravity_on = True + self.Npop = len(self.linear_bias) or 1 + self.Psingle = None + + def _init_survey_mask(self, reset=False): + """Initialise the survey mask and save it to disk in binary + format. + + The survey mask C(n) represents the angular selection function + of the survey. + """ + if self.local_mask_prefix: + mask_filename = f"{self.local_mask_prefix}_survey_mask_binary.h5" + else: + mask_filename = "survey_mask_binary.h5" + self.local_mask_path = os.path.join(self.modeldir, mask_filename) + + if not os.path.exists(self.local_mask_path) or reset or self.reset: + if self.survey_mask_path is not None: + survey_mask = np.load(self.survey_mask_path) + survey_mask_binary = (survey_mask > 0).astype(int) + del survey_mask + else: + survey_mask_binary = np.ones([self.size] * 3, dtype=int) + + with h5py.File(self.local_mask_path, "w") as f: + f.create_dataset("survey_mask_binary", data=survey_mask_binary) + del survey_mask_binary + collect() + + def _init_radial_selection(self, reset=False): + """Initialise the radial selection function R(r) and save it to + disk. + """ + if self.radial_selection == "multiple_lognormal": + from selfisys.selection_functions import LognormalSelection + + # Set the normalisation constants + if self.norm_csts is None: + self.norm_csts = [1.0] * self.Npop + + # Initialise the radial selection functions + if self.local_mask_prefix: + filename = f"{self.local_mask_prefix}_select_fct.h5" + else: + filename = "select_fct.h5" + self.local_select_path = os.path.join(self.modeldir, filename) + LogNorm = LognormalSelection( + self.L, + self.selection_params, + survey_mask_path=self.survey_mask_path, + local_select_path=self.local_select_path, + size=self.size, + ) + LogNorm.init_selection(reset=self.reset or reset) + del LogNorm + elif self.radial_selection is None: + if self.norm_csts is None: + self.norm_csts = 1.0 + else: + raise ValueError( + f"Unknown or unimplemented selection function: {self.radial_selection}" + ) + + def _PrintMessage(self, required_verbosity, message, verbosity=None): + """Print a message to standard output using PrintMessage from + pyselfi.utils. + """ + from selfisys.selfi_interface import PrintMessage + + PrintMessage(required_verbosity, message, verbosity or self.verbosity) + + def _indent(self): + """Indents the standard output using INDENT from + pyselfi.utils. + """ + from selfisys.selfi_interface import indent + + indent() + + def _unindent(self): + """Unindents the standard output using UNINDENT from + pyselfi.utils. + """ + from selfisys.selfi_interface import unindent + + unindent() + + def _save_cosmo(self, cosmo, fname_cosmo, force_cosmo=False): + """Save cosmological parameters in JSON format. + + Parameters + ---------- + cosmo : dict + Cosmological parameters (and infrastructure parameters) to + be saved. + fname_cosmo : str + Name of the output JSON file. + force_cosmo : bool, optional + If True, overwrite the file if it already exists. Default is + `False`. + """ + from os.path import exists + + if not exists(fname_cosmo) or force_cosmo: + from json import dump + + with open(fname_cosmo, "w") as fp: + dump(cosmo, fp) + + def _add_noise(self, g, seednoise, field=None): + """Add noise to a realisation in physical space. + + Parameters + ---------- + g : ndarray + Field to which the noise is added (modified in place). + seednoise : int or list of int + Seed to generate the noise realisation. + field : ndarray, optional + Selection function to apply to the input field. Default is + `None`. + + Returns + ------- + None + """ + if self.noise_std > 0: + from numpy import random, sqrt, ones_like + from h5py import File + + if seednoise is not None: + rng = random.default_rng(seednoise) + else: + raise ValueError("Seednoise must be provided and cannot be None.") + + if field is None: + field = ones_like(g) + + N = self.observed_density if self.observed_density is not None else 1.0 + noise = rng.normal( + size=(self.size, self.size, self.size), + scale=self.noise_std * sqrt(N * field), + ) + + with File(self.local_mask_path, "r") as f: + mask = f["survey_mask_binary"][:] + noise *= mask + + g += noise + del noise + collect() + + def _get_density_field(self, delta_g_dm, bias): + """Apply galaxy bias to a dark matter overdensity field. + + Parameters + ---------- + delta_g_dm : ndarray + Dark matter density contrast in physical space. + bias : float + Linear bias factor. + + Returns + ------- + delta_g : ndarray + Galaxy density or overdensity field. + """ + if bias is None: + bias = 1.0 + if not isinstance(bias, float): + raise TypeError("Bias must be a float.") + + if self.observed_density is None: + delta_g = bias * delta_g_dm + else: + delta_g = self.observed_density * (1 + bias * delta_g_dm) + + return delta_g + + def _repaint_and_get_Phi( + self, + g_obj, + norm, + seednoise, + bias=None, + field=None, + return_g=False, + AliasingCorr=True, + ): + """Repaint a realisation in physical space and compute its + summary statistics. + + Parameters + ---------- + g_obj : Field + Input field object. + norm : ndarray + Normalisation constants for the summary statistics. + seednoise : int or list of int + Seed to generate the noise realisation. + bias : float, optional + Bias to apply to the input field. Default is `None`. + field : ndarray, optional + Selection function. Reused as output to save memory + allocations. Default is `None`. + return_g : bool, optional + If True, returns the full field. Default is `False`. + AliasingCorr : bool, optional + Whether to apply aliasing correction. Default is `True`. + + Returns + ------- + Phi : ndarray + Vector of summary statistics. + delta_g : ndarray or None + Realisation in physical space if return_g is True; None + otherwise. + """ + import copy + from selfisys.sbmy_interface import compute_Phi + + if bias is not None and not isinstance(bias, float): + raise TypeError("Bias must be a float.") + + g_obj_local = copy.deepcopy(g_obj) + g_obj_local.data = self._get_density_field(g_obj_local.data, bias) + if field is not None: + g_obj_local.data *= field + + self._add_noise(g_obj_local.data, seednoise=seednoise, field=field) + Phi = compute_Phi( + self.G_ss_path, + self.P_ss_path, + g_obj_local, + norm, + AliasingCorr, + self.verbosity, + ) + + delta_g = copy.deepcopy(g_obj_local.data) if return_g else None + del g_obj_local + collect() + return Phi, delta_g + + def _apply_selection(self, fnames_outputdensity, seednoise, return_g=False, AliasingCorr=True): + """Apply the selection function to a realisation in physical + space. + + Parameters + ---------- + fnames_outputdensity : list of str + Filenames of the output density fields. + seednoise : int or list of int + Seed to generate the noise realisation. + return_g : bool, optional + If True, returns the full field(s). Default is `False`. + AliasingCorr : bool, optional + Whether to apply aliasing correction. Default is `True`. + + Returns + ------- + Phi_tot : ndarray + Concatenated summary statistics for each population. + gs : list of ndarray or None + List of full fields in physical space if return_g is True; + None otherwise. + """ + from pysbmy.field import read_basefield + + split = any(self.sim_params.startswith(s) for s in ["split", "custom"]) + if not split: + g_obj = read_basefield(fnames_outputdensity[0]) + elif self.force_neglect_lightcone: + g_obj = read_basefield(fnames_outputdensity[0]) + if self.radial_selection is not None: + Phi_tot = [] + gs = [] + for ifct in range(self.Npop): + if split and not self.force_neglect_lightcone: + g_obj = read_basefield(fnames_outputdensity[ifct]) + with h5py.File(self.local_select_path, "r") as f: + field = f["select_fct"][ifct].astype(float) + bias = float(self.linear_bias[ifct]) if self.linear_bias is not None else None + Phi, g_out = self._repaint_and_get_Phi( + g_obj, + self.norm_csts[ifct], + seednoise=seednoise, + bias=bias, + field=field, + return_g=return_g, + AliasingCorr=AliasingCorr, + ) + Phi_tot = np.concatenate([Phi_tot, Phi]) + if return_g: + gs.append(g_out) + del g_out + result = Phi_tot, gs if return_g else None + else: + from h5py import File + + if self.local_mask_path is not None: + with File(self.local_mask_path, "r") as f: + field = f["survey_mask_binary"][:] + else: + field = None + if not split: + bias = self.linear_bias if self.linear_bias is not None else None + Phi_tot, delta_g = self._repaint_and_get_Phi( + g_obj, + self.norm_csts, + seednoise=seednoise, + bias=bias, + field=field, + return_g=return_g, + AliasingCorr=AliasingCorr, + ) + result = (Phi_tot, [delta_g]) if return_g else (Phi_tot, None) + else: + Phi_tot = [] + gs = [] + for ifct in range(len(self.TimeSteps)): + bias = ( + self.linear_bias + if isinstance(self.linear_bias, float) + else (self.linear_bias[ifct] if self.linear_bias is not None else None) + ) + g_obj = read_basefield(fnames_outputdensity[ifct]) + Phi, g_out = self._repaint_and_get_Phi( + g_obj, + self.norm_csts, + seednoise=seednoise, + bias=bias, + field=field, + return_g=return_g, + AliasingCorr=AliasingCorr, + ) + Phi_tot = np.concatenate([Phi_tot, Phi]) + if return_g: + gs.append(g_out) + del g_out + result = Phi_tot, gs if return_g else None + del field, g_obj + collect() + return result + + def _setup_parfiles( + self, + d, + cosmology, + file_names, + force=False, + ): + """Sets up Simbelmynë parameter file given the necessary inputs + (please refer to the Simbelmynë documentation for more details). + + Parameters + ---------- + d : int + index giving the direction in parameter space: + -1 for mock data, 0 for the expansion point, or from 1 to S + cosmology : array, double, dimension=5 + cosmological parameters + file_names : dict + Dictionary containing the names of the input and output + files. + force : bool, optional, default=False + overwrite if files already exists? + + """ + from selfisys.sbmy_interface import setup_sbmy_parfiles + + hiddenbox_params = { + "Npop": self.Npop, + "TimeSteps": self.TimeSteps, + "eff_redshifts": self.eff_redshifts, + "sim_params": self.sim_params, + "Ntimesteps": self.Ntimesteps, + "TimeStepDistribution": self.TimeStepDistribution, + "modified_selfi": self.modified_selfi, + "Np0": self.Np0, + "Npm0": self.Npm0, + "size": self.size, + "L": self.L, + "fsimdir": self.fsimdir, + } + setup_sbmy_parfiles( + d, + cosmology, + file_names, + hiddenbox_params, + force, + ) + + def _run_sim( + self, + fname_simparfile, + fname_simlogs, + fnames_outputdensity, + force_sim=False, + check_output=False, + ): + """Run a simulation with Simbelmynë. + + Parameters + ---------- + fname_simparfile : str + Name of the input parameter file. + fname_simlogs : str + Name of the output Simbelmynë logs. + fnames_outputdensity : list of str + Names of the output density fields to be written. + force_sim : bool, optional + If True, force recomputation if output density already + exists. + Default is `False`. + check_output : bool, optional + If True, check the integrity of the output files and + recompute if corrupted. Default is `False`. + """ + from glob import glob + from selfisys.utils.parser import check_files_exist + + split = self.sim_params.startswith("split") + if not check_files_exist(fnames_outputdensity) or force_sim: + from pysbmy import pySbmy + + if not split: + pySbmy(f"{fname_simparfile}_{self.Npop}.sbmy", fname_simlogs) + else: + for i in range(self.Ntimesteps): + pySbmy(f"{fname_simparfile}_pop{i}.sbmy", fname_simlogs) + elif check_output: + from pysbmy.field import read_basefield + + try: + for fname in fnames_outputdensity: + g = read_basefield(fname) + del g + collect() + except Exception: + from pysbmy import pySbmy + + for fname in fnames_outputdensity: + os.remove(fname) + pySbmy(fname_simparfile, fname_simlogs) + + # Workaround to remove unwanted Simbelmynë outputs from disk + temp_files = glob(os.path.join(self.fsimdir, "data", "cola_kick_*.h5")) + for f in temp_files: + os.remove(f) + + if split: + temp_files = glob(os.path.join(self.fsimdir, "data", "cola_snapshot_*.h5")) + temp_files += glob(os.path.join(self.fsimdir, "data", "lpt_psi*.h5")) + for f in temp_files: + os.remove(f) + + def _compute_mocks( + self, + seednoise, + fnames_input, + fname_mocks, + return_g, + fname_g=None, + AliasingCorr=True, + ): + """Apply galaxy bias, observational effects, and compute the + summary statistics. + + Parameters + ---------- + seednoise : int or list of int + Seed to generate the noise realisation. + fnames_input : list of str + Filenames of the input density fields. + fname_mocks : str + Filename to save the computed summary statistics. + return_g : bool + If True, return the observed field(s). + fname_g : str, optional + Filename to save the observed field(s) if return_g is True. + AliasingCorr : bool, optional + Whether to apply aliasing correction. Default is `True`. + + Returns + ------- + Phi : ndarray + Vector of summary statistics. + g_out : ndarray or list of ndarray or None + Observed field(s) if return_g is True; otherwise None. + """ + + if return_g and fname_g is None: + raise ValueError("Filename for the observed field must be provided.") + + if (not os.path.exists(fname_mocks)) or self.force_recompute_mocks: + Phi, g_out = self._apply_selection( + fnames_input, + seednoise=seednoise, + return_g=return_g, + AliasingCorr=AliasingCorr, + ) + with h5py.File(fname_mocks, "w") as f: + f.create_dataset("Phi", data=Phi) + if return_g: + with h5py.File(fname_g, "w") as f: + f.create_dataset("g", data=g_out) + else: + self._PrintMessage(1, f"Using existing mock file: {fname_mocks}") + with h5py.File(fname_mocks, "r") as f: + Phi = f["Phi"][:] + if return_g: + with h5py.File(fname_g, "r") as f: + g_out = f["g"][:] + else: + g_out = None + + return Phi, g_out + + def _sample_omega(self, seed): + """Sample cosmological parameters from the prior. + + Parameters + ---------- + seed : int or list of int + Seed for the random number generator. + + Returns + ------- + omega_sample : ndarray + Sampled cosmological parameters. + """ + from selfisys.utils.tools import sample_omega_from_prior + from selfisys.global_parameters import planck_mean, planck_cov + + ids = list(range(len(planck_mean))) + return sample_omega_from_prior(1, planck_mean, planck_cov, ids, seed=seed)[0] + + def _aux_hiddenbox( + self, + d, + seedphase, + seednoise, + file_names, + force_parfiles=False, + force_sim=False, + force_phase=False, + return_g=False, + check_output=False, + RSDs=True, + sample=None, + ): + """Generate observations from the input initial matter power + spectrum. + + Parameters + ---------- + d : int + Index indicating the direction in parameter space (-1 for + mock data, 0 for the expansion point, or from 1 to S for the + gradient directions). + seedphase : int or list of int + Seed to generate the initial white noise in Fourier space. + seednoise : int or list of int + Seed to generate the observational noise realisation. + file_names : dict + Dictionary containing the names of the input and output + files. + force_parfiles : bool, optional + If True, force recomputation of the parameter files. Default + is `False`. + force_sim : bool, optional + If True, force recomputation of the simulation. Default is + `False`. + force_phase : bool, optional + If True, force recomputation of the phase. Default is + `False`. + return_g : bool, optional + If True, return the full realisation in physical space. + Default is `False`. + check_output : bool, optional + If True, check the integrity of the output file and + recompute if corrupted. Default is `False`. + RSDs : bool, optional + If True, include redshift-space distortions. Default is + `True`. + sample : ndarray, optional + Cosmological parameters sample. If `None`, sample from the + prior. Default is `None`. + + Returns + ------- + result : tuple + A tuple containing: + - Phi : (ndarray) + Summary statistics for each population, concatenated. + - g : (ndarray or list of ndarray or None) + List of observed fields if return_g is True; None + otherwise. + """ + fname_power_spectrum = file_names["fname_power_spectrum"] + fname_simparfile = file_names["fname_simparfile"] + fname_whitenoise = file_names["fname_whitenoise"] + seedname_whitenoise = file_names["seedname_whitenoise"] + fname_outputinitialdensity = file_names["fname_outputinitialdensity"] + fnames_outputrealspacedensity = file_names["fnames_outputrealspacedensity"] + fnames_outputdensity = file_names["fnames_outputdensity"] + fname_simlogs = file_names["fname_simlogs"] + fname_mocks = file_names["fname_mocks"] + fname_g = file_names["fname_g"] if return_g else None + + sample = self._sample_omega(seedphase) if sample is None else sample + + if self.gravity_on: + from selfisys.sbmy_interface import generate_white_noise_Field + + self._setup_parfiles( + d, + sample, + file_names, + force_parfiles, + ) + generate_white_noise_Field( + self.L, + self.size, + seedphase, + fname_whitenoise, + seedname_whitenoise, + force_phase, + ) + + if not self.setup_only: + if self.gravity_on: + self._run_sim( + fname_simparfile, + fname_simlogs, + fnames_outputdensity, + force_sim, + check_output, + ) + fnames = fnames_outputdensity if RSDs else fnames_outputrealspacedensity + else: + from selfisys.grf import primordial_grf + + primordial_grf( + self.L, + self.size, + seedphase, + fname_power_spectrum, + fname_outputinitialdensity, + force_sim, + False, + self.verbosity, + ) + fnames = [fname_outputinitialdensity] + + result = self._compute_mocks(seednoise, fnames, fname_mocks, return_g, fname_g) + else: + result = [], [] # lists are mandatory for compatibility + + return result + + def _power_spectrum_from_theta( + self, theta, fname_power_spectrum, theta_is_p=False, force=False + ): + """Compute the power spectrum values using spline interpolation. + + Parameters + ---------- + theta : ndarray + Vector of power spectrum values at the support wavenumbers. + fname_power_spectrum : str + Name of the input/output power spectrum file. + theta_is_p : bool, optional + If True, theta is already an unnormalised power spectrum. + Default is `False`. + force : bool, optional + If True, force recomputation. Default is `False`. + """ + from pysbmy.power import PowerSpectrum + + if (not os.path.exists(fname_power_spectrum)) or force: + from scipy.interpolate import InterpolatedUnivariateSpline + from pysbmy.power import FourierGrid + + PP = theta if theta_is_p else self.theta2P(theta) + Spline = InterpolatedUnivariateSpline(self.k_s, PP, k=5) + G_sim = FourierGrid.read(self.G_sim_path) + power_spectrum = Spline(G_sim.k_modes) + power_spectrum[0] = 0.0 + P = PowerSpectrum.from_FourierGrid(G_sim, powerspectrum=power_spectrum) + P.write(fname_power_spectrum) + del G_sim + collect() + + def _clean_output(self, fname_output): + """Remove a file from disk if it exists. + + Parameters + ---------- + fname_output : str + Name of the file to remove. + """ + if fname_output is not None and os.path.exists(fname_output): + os.remove(fname_output) + + def _get_current_seed(self, parent_seed, fixed_seed, i): + """Return the current seed for the i-th realisation. + + Parameters + ---------- + parent_seed : int or list of int + The parent seed. + fixed_seed : bool + If True, use the parent seed directly. + i : int + Index of the current realisation. + + Returns + ------- + this_seed : int or list of int + The seed for the current realisation. + """ + if fixed_seed: + this_seed = parent_seed + else: + this_seed = [i, parent_seed] + return this_seed diff --git a/src/selfisys/normalise_hb.py b/src/selfisys/normalise_hb.py new file mode 100644 index 0000000..a4c11f1 --- /dev/null +++ b/src/selfisys/normalise_hb.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +"""Tools to define normalisation constants for the hidden box.""" + +import os +import numpy as np +from typing import Tuple, Dict, Any + +from selfisys.global_parameters import MIN_K_NORMALISATION +from selfisys.hiddenbox import HiddenBox + + +def worker_normalisation( + hidden_box: HiddenBox, + params: Tuple[Dict[str, Any], list, list, bool], +) -> np.ndarray: + """Worker function to compute the normalisation constants, + compatible with Python multiprocessing. + + Parameters + ---------- + hidden_box : HiddenBox + Instance of the HiddenBox class. + params : tuple + A tuple containing (cosmo, seedphase, seednoise, force). + + Returns + ------- + phi : ndarray + Computed summary statistics. + """ + ( + cosmo, + seedphase, + seednoise, + force, + ) = params + name = ( + "norm" + + "__" + + "_".join([str(int(s)) for s in seedphase]) + + "__" + + "_".join([str(int(s)) for s in seednoise]) + ) + + if hidden_box.verbosity > 1: + hidden_box._PrintMessage(1, "Running simulation...") + hidden_box._indent() + phi = hidden_box.make_data( + cosmo, + name, + seedphase, + seednoise, + force, + force, + force, + force, + ) + hidden_box._unindent() + elif hidden_box.verbosity > 0: + from selfisys.utils.low_level import ( + stdout_redirector, + ) + from io import BytesIO + + f = BytesIO() + with stdout_redirector(f): + phi = hidden_box.make_data( + cosmo, + name, + seedphase, + seednoise, + force, + force, + force, + force, + ) + f.close() + else: + from selfisys.utils.low_level import ( + stdout_redirector, + stderr_redirector, + ) + from io import BytesIO + + f = BytesIO() + g = BytesIO() + with stdout_redirector(f), stderr_redirector(g): + phi = hidden_box.make_data( + cosmo, + name, + seedphase, + seednoise, + force, + force, + force, + force, + ) + f.close() + g.close() + + return phi + + +def worker_normalisation_wrapper(args): + """Wrapper function for the worker_normalisation function. + + Parameters + ---------- + args : tuple + A tuple containing (hidden_box, params). + + Returns + ------- + phi : ndarray + Computed summary statistics. + """ + hidden_box, params = args + return worker_normalisation(hidden_box, params) + + +def worker_normalisation_public( + hidden_box, + cosmo: Dict[str, Any], + N: int, + i: int, +): + """Run the i-th simulation required to compute the normalisation + constants. + + Parameters + ---------- + hidden_box : HiddenBox + Instance of the HiddenBox class. + cosmo : dict + Cosmological and some infrastructure parameters. + N : int + Total number of realisations required. + i : int + Index of the simulation to be computed. + """ + params = ( + cosmo, + [ + i, + hidden_box._HiddenBox__global_seednorm, + ], + [ + i + N, + hidden_box._HiddenBox__global_seednorm, + ], + False, + ) + worker_normalisation(hidden_box, params) + + +def define_normalisation( + hidden_box: HiddenBox, + Pbins: np.ndarray, + cosmo: Dict[str, Any], + N: int, + min_k_norma: float = MIN_K_NORMALISATION, + npar: int = 1, + force: bool = False, +) -> np.ndarray: + """Define the normalisation constants for the HiddenBox instance. + + Parameters + ---------- + hidden_box : HiddenBox + Instance of the HiddenBox class. + Pbins : ndarray + Array of P bin values. + cosmo : dict + Cosmological and infrastructure parameters. + N : int + Number of realisations required. + min_k_norma : float, optional + Minimum k value to compute the normalisation constants. + npar : int, optional + Number of parallel processes to use. Default is 1. + force : bool, optional + If True, force recomputation. Default is False. + + Returns + ------- + norm_csts : ndarray + Normalisation constants for the HiddenBox instance. + """ + import tqdm.auto as tqdm + from multiprocessing import Pool + + hidden_box._PrintMessage( + 0, + "Defining normalisation constants...", + ) + hidden_box._indent() + indices = np.where(Pbins > min_k_norma) + tasks = [ + ( + hidden_box, + ( + cosmo, + [ + i, + hidden_box._HiddenBox__global_seednorm, + ], + [ + i + N, + hidden_box._HiddenBox__global_seednorm, + ], + force, + ), + ) + for i in range(N) + ] + + ncors = os.cpu_count() + nprocs = min(npar, ncors) + + norm_csts_list = np.zeros((hidden_box._Npop, N)) + if npar > 1: + with Pool(nprocs) as p: + for j, val in enumerate( + tqdm.tqdm( + p.imap( + worker_normalisation_wrapper, + tasks, + ), + total=N, + ) + ): + norm_csts_list[:, j] = np.array( + [ + np.mean( + val[i * hidden_box.Psingle : (i + 1) * hidden_box.Psingle][indices] + ) + for i in range(hidden_box._Npop) + ] + ) + else: + for j, val in enumerate( + tqdm.tqdm( + map( + worker_normalisation_wrapper, + tasks, + ), + total=N, + ) + ): + val = np.array(val) + norm_csts_list[:, j] = np.array( + [ + np.mean(val[i * hidden_box.Psingle : (i + 1) * hidden_box.Psingle][indices]) + for i in range(hidden_box._Npop) + ] + ) + norm_csts = np.mean(norm_csts_list, axis=1) + hidden_box._unindent() + hidden_box._PrintMessage( + 0, + "Defining normalisation constants done.", + ) + + return norm_csts diff --git a/src/selfisys/pipeline/step0a.py b/src/selfisys/pipeline/step0a.py new file mode 100644 index 0000000..aa2726f --- /dev/null +++ b/src/selfisys/pipeline/step0a.py @@ -0,0 +1,480 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Step 0a of the SELFI pipeline. + +We use a blackbox forward data model with physics relying on COLA +simulations (using the Simbelmynë hierarchical probabilistic simulator). + +This step generates the Simbelmynë parameter files needed for +normalising the blackbox and computes the white noise fields. At this +stage, the only simulation performed is to compute the ground truth +spectrum. +""" + +import gc +import pickle +import numpy as np + +from os.path import exists +from pathlib import Path + +from selfisys.utils.parser import ArgumentParser, none_or_bool_or_str, bool_sh, intNone +from selfisys.global_parameters import * +from selfisys.setup_model import * +from selfisys.hiddenbox import HiddenBox +from selfisys.utils.tools import get_k_max +from selfisys.sbmy_interface import handle_time_stepping +from selfisys.utils.plot_utils import setup_plotting +from selfisys.normalise_hb import worker_normalisation_public +from selfisys.utils.logger import getCustomLogger, INDENT, UNINDENT + +logger = getCustomLogger(__name__) + +""" +Below is the core logic of step 0a. + +Raises +------ +OSError + If file or directory access fails. +RuntimeError + If unexpected issues occur (e.g., plotting or data generation + failures). +""" + +parser = ArgumentParser( + description=( + "Run the first step of the SelfiSys pipeline. " + "Generates Simbelmynë parameter files for blackbox normalisation." + ) +) + +parser.add_argument( + "--wd_ext", + type=str, + help=( + "Name of the working directory (relative to ROOT_PATH in " + "`../global_parameters.py`), ending with a slash." + ), +) +parser.add_argument( + "--name", + type=str, + default="std", + help=( + "Suffix to the working directory for this run. " + "White noise fields are shared between runs irrespective of name." + ), +) +parser.add_argument( + "--total_steps", + type=int, + default=None, + help="Number of timesteps.", +) +parser.add_argument( + "--aa", + type=float, + nargs="*", + default=None, + help="List of scale factors at which to synchronise kicks and drifts.", +) +parser.add_argument( + "--size", + type=int, + default=512, + help="Number of grid points in each direction.", +) +parser.add_argument( + "--Np0", + type=intNone, + default=1024, + help="Number of dark matter particles along each axis.", +) +parser.add_argument( + "--Npm0", + type=intNone, + default=1024, + help="Number of particle-mesh cells along each axis.", +) +parser.add_argument( + "--L", + type=int, + default=3600, + help="Size of the simulation box in Mpc/h.", +) +parser.add_argument( + "--S", + type=int, + default=64, + help="Number of support wavenumbers for the initial matter power spectrum.", +) +parser.add_argument( + "--Pinit", + type=int, + default=50, + help=( + "Max number of bins for summaries. Actual count may be smaller since it is automatically " + "tuned to ensure that each bin contains a sufficient number of modes." + ), +) +parser.add_argument( + "--Nnorm", + type=int, + default=10, + help="Number of simulations for summary normalisation.", +) +parser.add_argument( + "--Ne", + type=int, + default=300, + help="Number of simulations at the expansion point for blackbox linearisation.", +) +parser.add_argument( + "--Ns", + type=int, + default=10, + help="Number of simulations for each gradient component at the expansion point.", +) +parser.add_argument( + "--Delta_theta", + type=float, + default=1e-2, + help="Finite difference step size for gradient computation.", +) +parser.add_argument( + "--OUTDIR", + type=str, + help="Absolute path to the output directory.", +) +parser.add_argument( + "--prior", + type=str, + default="planck2018", + help='Prior type (e.g. "selfi2019", "planck2018", "planck2018_cv").', +) +parser.add_argument( + "--nsamples_prior", + type=int, + default=int(5e4), + help=( + "Number of samples for computing the prior on the initial power spectrum " + "(when using planck2018[_cv])." + ), +) +parser.add_argument( + "--radial_selection", + type=none_or_bool_or_str, + default="multiple_lognormal", + help=( + "Radial selection function. " + 'Set to "multiple_lognormal" for multi-population lognormal selection.' + ), +) +parser.add_argument( + "--selection_params", + type=float, + nargs="*", + help="Parameters for the radial selection function (see hiddenbox.py).", +) +parser.add_argument( + "--survey_mask_path", + type=none_or_bool_or_str, + default=None, + help="Absolute path to the survey mask (if any).", +) +parser.add_argument( + "--sim_params", + type=none_or_bool_or_str, + default=None, + help="Parameters for the gravity solver.", +) +parser.add_argument( + "--lin_bias", + type=float, + nargs="*", + help="Linear biases.", +) +parser.add_argument( + "--obs_density", + type=none_or_bool_or_str, + default=None, + help="Observed density.", +) +parser.add_argument( + "--noise", + type=float, + default=0.1, + help="Noise level.", +) +parser.add_argument( + "--force", + type=bool_sh, + default=False, + help="Force recomputations if True.", +) + +args = parser.parse_args() + +if __name__ == "__main__": + try: + wd_ext = args.wd_ext + name = args.name + total_steps = args.total_steps + aa = args.aa + size = args.size + Np0 = args.Np0 + Npm0 = args.Npm0 + L = args.L + S = args.S + Pinit = args.Pinit + Nnorm = args.Nnorm + Ne = args.Ne + Ns = args.Ns + Delta_theta = args.Delta_theta + OUTDIR = args.OUTDIR + prior_type = args.prior + nsamples_prior = int(args.nsamples_prior) + radial_selection = args.radial_selection + if radial_selection == "multiple_lognormal": + selection_params = np.reshape(np.array(args.selection_params), (3, -1)) + else: + logger.error("Radial selection not yet implemented.") + raise NotImplementedError("Only 'multiple_lognormal' is supported at present.") + survey_mask_path = args.survey_mask_path + sim_params = args.sim_params + isstd = sim_params[:3] == "std" + splitLPT = sim_params[:8] == "splitLPT" + gravity_on = sim_params[:6] != "nograv" + if isinstance(args.lin_bias, list): + lin_bias = np.array(args.lin_bias) + else: + lin_bias = args.lin_bias + Npop = len(lin_bias) if isinstance(lin_bias, np.ndarray) else 1 + obs_density = args.obs_density + noise = args.noise + force = args.force + + # Configure plotting aesthetics for consistent visualisation + setup_plotting() + + # Create directories + wd_noname = f"{OUTDIR}{wd_ext}{size}{int(L)}{Pinit}{Nnorm}/" + wd = wd_noname + name + "/" + modeldir = wd + "model/" + figuresdir = wd + "Figures/" + + Path(wd + "RESULTS/").mkdir(parents=True, exist_ok=True) + Path(modeldir).mkdir(parents=True, exist_ok=True) + Path(wd_noname + "wn/").mkdir(parents=True, exist_ok=True) + Path(wd + "data/").mkdir(parents=True, exist_ok=True) + Path(figuresdir).mkdir(parents=True, exist_ok=True) + Path(wd + "pool/").mkdir(parents=True, exist_ok=True) + Path(wd + "score_compression/").mkdir(parents=True, exist_ok=True) + + for d in range(S + 1): + dirsims = wd + f"pool/d{d}/" + Path(dirsims).mkdir(parents=True, exist_ok=True) + + np.save(modeldir + "radial_selection.npy", radial_selection) + np.save(modeldir + "selection_params.npy", selection_params) + np.save(modeldir + "lin_bias.npy", lin_bias) + np.save(modeldir + "obs_density.npy", obs_density) + np.save(modeldir + "noise.npy", noise) + + logger.info("Setting up model parameters...") + + k_max = get_k_max(L, size) # k_max in h/Mpc + logger.info("Maximum wavenumber: k_max = %f", k_max) + # Cosmo at the expansion point: + params_planck = params_planck_kmax_missing.copy() + params_planck["k_max"] = k_max + # Fiducial BBKS spectrum for normalisation: + params_BBKS = params_BBKS_kmax_missing.copy() + params_BBKS["k_max"] = k_max + # Observed cosmology: + params_cosmo_obs = params_cosmo_obs_kmax_missing.copy() + params_cosmo_obs["k_max"] = k_max + + params = setup_model( + workdir=modeldir, + params_planck=params_planck, + params_P0=params_BBKS, + size=size, + L=L, + S=S, + Pinit=Pinit, + force=True, + ) + gc.collect() + + ( + size, + L, + P, + S, + G_sim_path, + G_ss_path, + Pbins_bnd, + Pbins, + k_s, + P_ss_obj_path, + P_0, + planck_Pk_EH, + ) = params + + other_params = { + "size": size, + "P": P, + "Np0": Np0, + "Npm0": Npm0, + "L": L, + "S": S, + "total_steps": total_steps, + "aa": aa, + "G_sim_path": G_sim_path, + "G_ss_path": G_ss_path, + "P_ss_obj_path": P_ss_obj_path, + "Pinit": Pinit, + "Nnorm": Nnorm, + "Ne": Ne, + "Ns": Ns, + "Delta_theta": Delta_theta, + "sim_params": sim_params, + } + with open(modeldir + "other_params.pkl", "wb") as f: + pickle.dump(other_params, f) + + # Save a human readable record of the parameters + with open(wd + "params.txt", "w") as f: + f.write("Parameters for this run:\n") + f.write("size: " + str(size) + "\n") + f.write("Np0: " + str(Np0) + "\n") + f.write("Npm0: " + str(Npm0) + "\n") + f.write("L: " + str(L) + "\n") + f.write("S: " + str(S) + "\n") + f.write("Pinit: " + str(Pinit) + "\n") + f.write("P: " + str(P) + "\n") + f.write("Nnorm: " + str(Nnorm) + "\n") + f.write("total_steps: " + str(total_steps) + "\n") + f.write("aa: " + str(aa) + "\n") + f.write("Ne: " + str(Ne) + "\n") + f.write("Ns: " + str(Ns) + "\n") + f.write("Delta_theta: " + str(Delta_theta) + "\n") + f.write("OUTDIR: " + OUTDIR + "\n") + f.write("prior_type: " + prior_type + "\n") + f.write("nsamples_prior: " + str(nsamples_prior) + "\n") + f.write("radial_selection: " + str(radial_selection) + "\n") + f.write("selection_params:\n" + str(selection_params) + "\n") + f.write("survey_mask_path: " + str(survey_mask_path) + "\n") + f.write("lin_bias: " + str(lin_bias) + "\n") + f.write("obs_density: " + str(obs_density) + "\n") + f.write("noise: " + str(noise) + "\n") + f.write("sim_params: " + str(sim_params) + "\n") + + logger.info("Setting up model parameters done.") + + logger.info("Generating ground truth spectrum...") + gt_path = modeldir + "theta_gt.npy" + if not exists(gt_path) or force: + from pysbmy.power import get_Pk + + theta_gt = get_Pk(k_s, params_cosmo_obs) + np.save(gt_path, theta_gt) + del theta_gt + logger.info("Generating ground truth spectrum done.") + + def theta2P(theta): + return theta * P_0 + + merged_path, indices_steps_cumul, eff_redshifts = handle_time_stepping( + aa=aa, + total_steps=total_steps, + modeldir=modeldir, + figuresdir=figuresdir, + sim_params=sim_params, + force=force, + ) + + # Instantiate the HiddenBox object + logger.info("Instantiating the HiddenBox...") + HB_selfi = HiddenBox( + k_s=k_s, + P_ss_path=P_ss_obj_path, + Pbins_bnd=Pbins_bnd, + theta2P=theta2P, + P=P * Npop, + size=size, + L=L, + G_sim_path=G_sim_path, + G_ss_path=G_ss_path, + Np0=Np0, + Npm0=Npm0, + fsimdir=wd[:-1], + noise_std=noise, + radial_selection=radial_selection, + selection_params=selection_params, + observed_density=obs_density, + linear_bias=lin_bias, + norm_csts=None, + survey_mask_path=survey_mask_path, + local_mask_prefix=None, + sim_params=sim_params, + TimeStepDistribution=merged_path, + TimeSteps=indices_steps_cumul, + eff_redshifts=eff_redshifts, + seedphase=BASELINE_SEEDPHASE, + seednoise=BASELINE_SEEDNOISE, + fixnoise=False, + seednorm=BASELINE_SEEDNORM, + reset=True, + save_frequency=5, + ) + logger.info("Instantiating the HiddenBox done.") + + logger.info("Generating Simbelmynë parameter files for normalisation...") + if gravity_on: + HB_selfi.switch_setup() + INDENT() + for i in range(Nnorm): + logger.diagnostic("Setting Simbelmynë file %d/%d...", i + 1, Nnorm, verbosity=1) + worker_normalisation_public(HB_selfi, params_planck, Nnorm, i) + logger.diagnostic("Setting Simbelmynë file %d/%d done.", i + 1, Nnorm, verbosity=1) + if gravity_on: + HB_selfi.switch_setup() + + if prior_type == "selfi2019": + logger.diagnostic("Computing cosmic variance alpha_cv...") + compute_alpha_cv( + workdir=modeldir, + k_s=k_s, + size=size, + L=L, + window_fct_path=wd[:-1] + "/model/select_fct.h5", + force=True, + ) + logger.diagnostic("Computing cosmic variance alpha_cv done.") + UNINDENT() + logger.info("Generating Simbelmynë parameter files for normalisation done.") + + except OSError as e: + logger.error("Directory or file access error: %s", str(e)) + raise + except Exception as e: + logger.critical("An unexpected error occurred: %s", str(e)) + raise RuntimeError("Pipeline step 0a failed.") from e + finally: + gc.collect() + logger.info("step 0a of the SelfiSys pipeline: done.") diff --git a/src/selfisys/pipeline/step0b.py b/src/selfisys/pipeline/step0b.py new file mode 100644 index 0000000..cb09dd6 --- /dev/null +++ b/src/selfisys/pipeline/step0b.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Step 0b of the SelfiSys pipeline. + +Run all the Simbelmynë simulations required to normalise the HiddenBox. + +This script invokes the pySbmy interface to launch Simbelmynë +simulations in parallel. +""" + +import os +from pathlib import Path +import gc +import numpy as np + +from selfisys.utils.parser import ArgumentParser, bool_sh +from selfisys.utils.logger import getCustomLogger + +logger = getCustomLogger(__name__) + +parser = ArgumentParser(description="Run Simbelmynë for step 0b of the SelfiSys pipeline.") +parser.add_argument("--pool_path", type=str, help="Path to the pool of simulations.") +parser.add_argument("--ii", type=int, nargs="+", help="Indices of simulations to run.") +parser.add_argument( + "--npar", type=int, help="Number of simulations to run in parallel.", default=4 +) +parser.add_argument("--force", type=bool_sh, help="Force the computations.", default=False) +args = parser.parse_args() + +pool_path = args.pool_path +npar = args.npar +force = args.force +ii = np.array(args.ii, dtype=int) + +# If 'ii' is [-1], find simulation indices from the pool directory +if len(ii) == 1 and ii[0] == -1: + try: + ii = np.array( + [ + int(f.split("norm__")[1].split("_")[0]) + for f in os.listdir(pool_path) + if f.startswith("sim_norm") and f.endswith(".sbmy") + ], + dtype=int, + ) + except OSError as e: + logger.error("Failed to list files in '%s': %s", pool_path, str(e)) + raise + +nsim = len(ii) + + +def worker_norm(i): + """ + Run a Simbelmynë simulation to normalise the HiddenBox. + + Parameters + ---------- + i : int + Index specifying which simulation file to run. + + Raises + ------ + OSError + If file or directory access fails. + RuntimeError + If pySbmy encounters an unexpected error or the simulation fails. + """ + from pysbmy import pySbmy + from selfisys.utils.low_level import stdout_redirector, stderr_redirector + from io import BytesIO + + file_prefix = "sim_norm__" + str(i) + try: + # Find the simulation file corresponding to this index + suffix = [str(f) for f in os.listdir(pool_path) if f.startswith(file_prefix)][0] + fname_simparfile = Path(pool_path) / suffix + + # Derive output and logs filenames + base_out = suffix.split(".")[0].split("sim_")[1] + fname_output = Path(pool_path) / f"output_density_{base_out}.h5" + fname_simlogs = Path(pool_path) / f"{file_prefix}.txt" + + # Skip if the output file already exists + if fname_output.exists() and not force: + logger.info( + "Output file %s already exists, skipping simulation index %d...", fname_output, i + ) + return + + # Suppress or capture stdout/stderr for live monitoring purposes + f = BytesIO() + g = BytesIO() + with stdout_redirector(f): + with stderr_redirector(g): + pySbmy(str(fname_simparfile), str(fname_simlogs)) + g.close() + f.close() + + except OSError as e: + logger.error("File or directory access error while running index %d: %s", i, str(e)) + raise + except Exception as e: + logger.critical("Unexpected error in worker_norm (index %d): %s", i, str(e)) + raise RuntimeError(f"Simulation index {i} failed.") from e + + +if __name__ == "__main__": + from tqdm import tqdm + from multiprocessing import Pool + + try: + logger.info("Running the simulations to normalise the HiddenBox...") + with Pool(processes=npar) as pool: + for _ in tqdm(pool.imap(worker_norm, ii), total=nsim): + pass + logger.info("Running the simulations to normalise the HiddenBox done.") + + except OSError as e: + logger.error("Pool or directory error: %s", str(e)) + raise + except Exception as e: + logger.critical("An unexpected error occurred during step 0b: %s", str(e)) + raise RuntimeError("Step 0b failed.") from e + finally: + gc.collect() + logger.info("step 0b of the SelfiSys pipeline: done.") diff --git a/src/selfisys/pipeline/step0c.py b/src/selfisys/pipeline/step0c.py new file mode 100644 index 0000000..e9fbdc3 --- /dev/null +++ b/src/selfisys/pipeline/step0c.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Step 0c of the SelfiSys pipeline. + +Compute the normalisation constants (based on the simulations performed +in step 0b using LPT or COLA) for the SelfiSys pipeline. +""" + +import gc +import numpy as np + +from selfisys.utils.parser import ArgumentParser, none_or_bool_or_str, bool_sh, safe_npload +from selfisys.global_parameters import * +from selfisys.utils.tools import get_k_max +from selfisys.utils.logger import getCustomLogger, INDENT, UNINDENT + +logger = getCustomLogger(__name__) + +parser = ArgumentParser( + description=( + "Step 0c of the SelfiSys pipeline. " + "Compute the normalisation constants based on the simulations performed in step 0b." + ) +) +parser.add_argument("--wd", type=str, help="Absolute path of the working directory.") +parser.add_argument( + "--npar_norm", + type=int, + help=( + "Number of simulations to load in parallel when computing the summaries. " + "Note that the overdensity fields were already computed at step 0b." + ), +) +parser.add_argument( + "--survey_mask_path", + type=none_or_bool_or_str, + default=None, + help="Path to the survey mask for the well-specified model.", +) +parser.add_argument( + "--effective_volume", + type=bool_sh, + default=False, + help="Use the effective volume to compute alpha_cv.", +) +parser.add_argument( + "--norm_csts_path", + type=none_or_bool_or_str, + default=None, + help="Path to external normalisation constants. Mandatory for test_gravity=True.", +) +parser.add_argument( + "--force", + type=bool_sh, + default=False, + help="Force the recomputation of the mocks.", +) + +args = parser.parse_args() + +wd = args.wd +npar_norm = args.npar_norm +survey_mask_path = args.survey_mask_path +effective_volume = args.effective_volume +norm_csts_path = args.norm_csts_path +force = args.force + +modeldir = wd + "model/" + +# Consistency check: 'npar_norm' and 'norm_csts_path' are mutually exclusive +if not (npar_norm is None) ^ (norm_csts_path is None): + raise ValueError("npar_norm and norm_csts_path are mutually exclusive.") + +if __name__ == "__main__": + try: + # If the user normalisation constants are provided, load them + if norm_csts_path is not None: + INDENT() + logger.info("Loading normalisation constants...") + if not exists(norm_csts_path): + raise ValueError("Normalisation constants not found.") + else: + norm_csts = np.load(norm_csts_path) + np.save(modeldir + "norm_csts.npy", norm_csts) + logger.info( + "External normalisation constants loaded and saved to model directory." + ) + UNINDENT() + else: + # Otherwise, compute normalisation constants from simulation data + from os.path import exists + import pickle + from pysbmy.timestepping import read_timestepping + from selfisys.hiddenbox import HiddenBox + from selfisys.normalise_hb import define_normalisation + + logger.info("Loading main parameters from 'other_params.pkl'...") + with open(modeldir + "other_params.pkl", "rb") as f: + other_params = pickle.load(f) + + size = other_params["size"] + Np0 = other_params["Np0"] + Npm0 = other_params["Npm0"] + L = other_params["L"] + S = other_params["S"] + total_steps = other_params["total_steps"] + aa = other_params["aa"] + P = other_params["P"] + G_sim_path = other_params["G_sim_path"] + G_ss_path = other_params["G_ss_path"] + P_ss_obj_path = other_params["P_ss_obj_path"] + Nnorm = other_params["Nnorm"] + sim_params = other_params["sim_params"] + + isstd = sim_params[:3] == "std" + + # Load radial selection + radial_selection = np.load(modeldir + "radial_selection.npy", allow_pickle=True) + if radial_selection is None: + radial_selection = None + selection_params = np.load(modeldir + "selection_params.npy") + lin_bias = np.load(modeldir + "lin_bias.npy") + Npop = len(lin_bias) if isinstance(lin_bias, np.ndarray) else 1 + obs_density = safe_npload(modeldir + "obs_density.npy") + noise = np.load(modeldir + "noise.npy") + + k_max = get_k_max(L, size) # k_max in h/Mpc + # Cosmology at the expansion point: + params_planck = params_planck_kmax_missing.copy() + params_planck["k_max"] = k_max + + Pbins_bnd = np.load(modeldir + "Pbins_bnd.npy") + Pbins = np.load(modeldir + "Pbins.npy") + k_s = np.load(modeldir + "k_s.npy") + P_0 = np.load(modeldir + "P_0.npy") + + def theta2P(theta): + return theta * P_0 + + # Set up the merged time-stepping if needed + if not isstd: + logger.info("Setting up time-stepping...") + nsteps = [ + round((aa[i + 1] - aa[i]) / (aa[-1] - aa[0]) * total_steps) + for i in range(len(aa) - 1) + ] + if sum(nsteps) != total_steps: + nsteps[nsteps.index(max(nsteps))] += total_steps - sum(nsteps) + indices_steps_cumul = list(np.cumsum(nsteps) - 1) + merged_path = modeldir + "merged.h5" + TS_merged = read_timestepping(merged_path) + + if sim_params.startswith("custom") or sim_params.startswith("nograv"): + TimeStepDistribution = merged_path + eff_redshifts = 1 / aa[-1] - 1 + else: + raise NotImplementedError("Time-stepping strategy not yet implemented.") + logger.info("Setting up time-stepping done.") + else: + TimeStepDistribution = None + eff_redshifts = None + indices_steps_cumul = None + + logger.info("Instantiating the HiddenBox for normalisation constants...") + HB_selfi = HiddenBox( + k_s=k_s, + P_ss_path=P_ss_obj_path, + Pbins_bnd=Pbins_bnd, + theta2P=theta2P, + P=P * Npop, + size=size, + L=L, + G_sim_path=G_sim_path, + G_ss_path=G_ss_path, + Np0=Np0, + Npm0=Npm0, + fsimdir=wd[:-1], + noise_std=noise, + radial_selection=radial_selection, + selection_params=selection_params, + observed_density=obs_density, + linear_bias=lin_bias, + norm_csts=None, + survey_mask_path=survey_mask_path, + local_mask_prefix=None, + sim_params=sim_params, + TimeStepDistribution=TimeStepDistribution, + TimeSteps=indices_steps_cumul, + eff_redshifts=eff_redshifts, + seedphase=BASELINE_SEEDPHASE, + seednoise=BASELINE_SEEDNOISE, + fixnoise=False, + seednorm=BASELINE_SEEDNORM, + reset=False, + save_frequency=5, + ) + logger.info("Instantiating the HiddenBox for normalisation constants done.") + + # Compute normalisation constants + if not exists(modeldir + "norm_csts.npy") or force: + if force: + HB_selfi.switch_recompute_pool() + norm_csts = define_normalisation( + HB_selfi, + Pbins, + params_planck, + Nnorm, + min_k_norma=MIN_K_NORMALISATION, + npar=1, + force=force, + ) + if force: + HB_selfi.switch_recompute_pool() + np.save(modeldir + "norm_csts.npy", norm_csts) + logger.info("Normalisation constants computed and saved.") + else: + logger.info("Normalisation constants already exist, skipping re-computation.") + norm_csts = np.load(modeldir + "norm_csts.npy") + + logger.info("Normalisation constants: %s", norm_csts) + + except OSError as e: + logger.error("File or directory access error in step 0c: %s", str(e)) + raise + except Exception as e: + logger.critical("Unexpected error occurred in step 0c: %s", str(e)) + raise RuntimeError("Step 0c failed.") from e + finally: + gc.collect() + logger.info("step 0c of the SelfiSys pipeline: done.") diff --git a/src/selfisys/pipeline/step0d.py b/src/selfisys/pipeline/step0d.py new file mode 100644 index 0000000..04d5a23 --- /dev/null +++ b/src/selfisys/pipeline/step0d.py @@ -0,0 +1,334 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Step 0d of the SelfiSys pipeline. + +Generate the observations using the ground truth cosmology. +""" + +import pickle +import gc +from os.path import exists +import numpy as np + +from selfisys.utils.parser import ( + ArgumentParser, + none_or_bool_or_str, + bool_sh, + joinstrs, + safe_npload, +) +from selfisys.global_parameters import * +from selfisys.utils.tools import get_k_max +from selfisys.hiddenbox import HiddenBox +from selfisys.utils.logger import getCustomLogger, INDENT, UNINDENT + +logger = getCustomLogger(__name__) + +parser = ArgumentParser( + description=( + "Step 0d of the SelfiSys pipeline. " + "Generate the observations using the ground truth cosmology." + ) +) +parser.add_argument("--wd", type=str, help="Absolute path of the working directory.") +parser.add_argument( + "--prefix_mocks", + type=none_or_bool_or_str, + default=None, + help="Prefix for the mock files.", +) +parser.add_argument( + "--survey_mask_path", + type=none_or_bool_or_str, + default=None, + help="Path to the survey mask for the well-specified model.", +) +parser.add_argument( + "--effective_volume", + type=bool_sh, + default=False, + help="Use the effective volume to compute alpha_cv.", +) +parser.add_argument( + "--name_obs", + type=none_or_bool_or_str, + default=None, + help="Prefix for the observation files. If None, uses default name. " + "Can be used for different data vectors.", +) +parser.add_argument( + "--reset_window_function", + type=bool_sh, + default=False, + help="Reset the window function.", +) +parser.add_argument( + "--neglect_lightcone", + type=bool_sh, + default=False, + help="Neglect lightcone effects even if snapshots at multiple redshifts are available.", +) +parser.add_argument( + "--force_obs", + type=bool_sh, + default=False, + help="Recompute the observations (e.g., to try a new cosmology).", +) +parser.add_argument( + "--copy_obs_from", + type=none_or_bool_or_str, + default=None, + help="Copy the observations from another project.", +) +parser.add_argument( + "--copy_fields", + type=bool_sh, + default=False, + help="Copy the fields from another project.", +) +parser.add_argument( + "--save_g", + type=bool_sh, + default=False, + help="Save the observed fields (g).", +) + +args = parser.parse_args() + +wd = args.wd +survey_mask_path = args.survey_mask_path +effective_volume = args.effective_volume +prefix_mocks = args.prefix_mocks +name_obs = "_" + args.name_obs if args.name_obs is not None else None +local_mask_prefix = args.name_obs if args.name_obs is not None else None +reset_window_function = args.reset_window_function +neglect_lightcone = args.neglect_lightcone +force_obs = args.force_obs +copy_obs_from = args.copy_obs_from +copy_fields = args.copy_fields +save_g = args.save_g + +if copy_obs_from is not None and name_obs is None: + raise ValueError( + "If you want to copy the observations from another project, " + "you must specify a name for the observation files." + ) +if copy_fields and copy_obs_from is None: + raise ValueError( + "If you want to copy the fields from another project, " + "you must specify the project to copy from." + ) + +if __name__ == "__main__": + from pysbmy.timestepping import read_timestepping + + try: + logger.info("Starting Step 0d of the SelfiSys pipeline.") + logger.info("Setting up main parameters...") + + modeldir = wd + "model/" + datadir = wd + "data/" + + # Copy the observations from another directory if specified + if copy_obs_from is not None: + from glob import glob + import shutil + + logger.info("Copying observations from: %s", copy_obs_from) + INDENT() + theta_gt_path = joinstrs([copy_obs_from, "model/theta_gt", name_obs, ".npy"]) + phi_obs_path = joinstrs([copy_obs_from, "model/phi_obs", name_obs, ".npy"]) + field_prefix = joinstrs([copy_obs_from, "data/output_density_obs", name_obs, "_"]) + + if not exists(theta_gt_path): + raise FileNotFoundError(f"{theta_gt_path} not found. Check the path.") + if not exists(phi_obs_path): + raise FileNotFoundError(f"{phi_obs_path} not found. Check the path.") + if len(glob(field_prefix + "*")) == 0: + raise FileNotFoundError( + f"No files starting with {field_prefix} found. Check the path." + ) + + logger.diagnostic("Copying theta_gt and phi_obs files...") + shutil.copy(theta_gt_path, f"{modeldir}theta_gt{name_obs}.npy") + shutil.copy(phi_obs_path, f"{modeldir}phi_obs{name_obs}.npy") + logger.diagnostic("Copying theta_gt and phi_obs files done.") + + if copy_fields: + logger.diagnostic("Copying full fields...") + for file in glob(field_prefix + "*"): + shutil.copy(file, datadir) + logger.diagnostic("Copying full fields done.") + UNINDENT() + else: + # Generating new observations + if prefix_mocks is not None: + modeldir_refined = modeldir + prefix_mocks + "/" + else: + modeldir_refined = modeldir + + with open(modeldir + "other_params.pkl", "rb") as f: + other_params = pickle.load(f) + size = other_params["size"] + Np0 = other_params["Np0"] + Npm0 = other_params["Npm0"] + L = other_params["L"] + S = other_params["S"] + total_steps = other_params["total_steps"] + aa = other_params["aa"] + P = other_params["P"] + G_sim_path = other_params["G_sim_path"] + G_ss_path = other_params["G_ss_path"] + P_ss_obj_path = other_params["P_ss_obj_path"] + sim_params_base = other_params["sim_params"] + + isstd = sim_params_base[:3] == "std" + if isstd and copy_obs_from is None: + # Workaround so that observations can be computed + sim_params_base = sim_params_base + "0" + sim_params = sim_params_base + BASEID_OBS + + radial_selection = safe_npload(modeldir + "radial_selection.npy") + selection_params = np.load(modeldir + "selection_params.npy") + lin_bias = np.load(modeldir + "lin_bias.npy") + Npop = len(lin_bias) if isinstance(lin_bias, np.ndarray) else 1 + obs_density = safe_npload(modeldir + "obs_density.npy") + noise = np.load(modeldir + "noise.npy") + + k_max = get_k_max(L, size) # k_max in h/Mpc + params_cosmo_obs = params_cosmo_obs_kmax_missing.copy() + params_cosmo_obs["k_max"] = k_max + + logger.diagnostic("Loading main parameters.") + Pbins_bnd = np.load(modeldir + "Pbins_bnd.npy") + Pbins = np.load(modeldir + "Pbins.npy") + k_s = np.load(modeldir + "k_s.npy") + P_0 = np.load(modeldir + "P_0.npy") + + def theta2P(theta): + return theta * P_0 + + # Setup time-stepping if needed + if not isstd: + logger.info("Setting up the time-stepping for non-standard approach...") + nsteps = [ + round((aa[i + 1] - aa[i]) / (aa[-1] - aa[0]) * total_steps) + for i in range(len(aa) - 1) + ] + if sum(nsteps) != total_steps: + nsteps[nsteps.index(max(nsteps))] += total_steps - sum(nsteps) + indices_steps_cumul = list(np.cumsum(nsteps) - 1) + merged_path = modeldir + "merged.h5" + TS_merged = read_timestepping(merged_path) + + if sim_params[:6] in ["custom", "nograv"]: + TimeStepDistribution = merged_path + eff_redshifts = 1 / aa[-1] - 1 + else: + raise NotImplementedError("Time-stepping strategy not yet implemented.") + logger.info("Setting up the time-stepping for non-standard approach done.") + else: + TimeStepDistribution = None + eff_redshifts = None + indices_steps_cumul = None + + logger.info("Instantiating the HiddenBox...") + # Load normalisation constants + if not exists(modeldir + "norm_csts.npy"): + raise ValueError("Normalisation constants not found.") + norm_csts = np.load(modeldir + "norm_csts.npy") + + BB_selfi = HiddenBox( + k_s=k_s, + P_ss_path=P_ss_obj_path, + Pbins_bnd=Pbins_bnd, + theta2P=theta2P, + P=P * Npop, + size=size, + L=L, + G_sim_path=G_sim_path, + G_ss_path=G_ss_path, + Np0=Np0, + Npm0=Npm0, + fsimdir=wd[:-1], + modeldir=modeldir_refined, + noise_std=noise, + radial_selection=radial_selection, + selection_params=selection_params, + observed_density=obs_density, + linear_bias=lin_bias, + norm_csts=norm_csts, + survey_mask_path=survey_mask_path, + local_mask_prefix=local_mask_prefix, + sim_params=sim_params, + TimeStepDistribution=TimeStepDistribution, + TimeSteps=indices_steps_cumul, + eff_redshifts=eff_redshifts, + seedphase=BASELINE_SEEDPHASE, + seednoise=BASELINE_SEEDNOISE, + fixnoise=False, + seednorm=BASELINE_SEEDNORM, + reset=reset_window_function, + save_frequency=5, + ) + logger.info("Instantiating the HiddenBox done.") + + # Generate the ground truth spectrum + if force_obs or not exists(joinstrs([modeldir, "theta_gt", name_obs, ".npy"])): + logger.info("Generating ground truth spectrum for Step 0d.") + from pysbmy.power import get_Pk + + theta_gt = get_Pk(k_s, params_cosmo_obs) + np.save(joinstrs([modeldir, "theta_gt", name_obs]), theta_gt) + logger.info("Generating ground truth spectrum for Step 0d done.") + + logger.info("Generating observations...") + phi_obs_path = joinstrs([modeldir, "phi_obs", name_obs, ".npy"]) + if not exists(phi_obs_path) or force_obs: + if neglect_lightcone: + BB_selfi.update(_force_neglect_lightcone=True) + d_obs = -1 + BB_selfi.switch_recompute_pool() + res = BB_selfi.make_data( + cosmo=params_cosmo_obs, + id=joinstrs([BASEID_OBS, name_obs]), + seedphase=SEEDPHASE_OBS, + seednoise=SEEDNOISE_OBS, + d=d_obs, + force_powerspectrum=force_obs, + force_parfiles=force_obs, + force_sim=force_obs, + force_cosmo=force_obs, + return_g=save_g, + ) + BB_selfi.switch_recompute_pool() + + if save_g: + phi_obs, _ = res + else: + phi_obs = res + + np.save(phi_obs_path, phi_obs) + logger.info("Generating observations done.") + + except OSError as e: + logger.error("File or directory access error during Step 0d: %s", str(e)) + raise + except Exception as e: + logger.critical("Unexpected error occurred in Step 0d: %s", str(e)) + raise RuntimeError("Step 0d failed.") from e + finally: + gc.collect() + logger.info("Step 0d of the SelfiSys pipeline: done.") diff --git a/src/selfisys/pipeline/step0e.py b/src/selfisys/pipeline/step0e.py new file mode 100644 index 0000000..9fbf867 --- /dev/null +++ b/src/selfisys/pipeline/step0e.py @@ -0,0 +1,551 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Step 0e of the SelfiSys pipeline. + +Generate all the Simbelmyne parameter files to run the simulations at +the expansion point, in all parameter space directions, in order to +linearise the HiddenBox. + +Unless the forward model is run in no-gravity mode, the only simulation +actually run here is the one to generate the prior on the initial +spectrum (if using planck2018[_cv]), based on cosmological parameters +drawn from the prior. +""" + +import gc +import pickle +import numpy as np +from pathlib import Path +from os.path import exists + +from selfisys.utils.parser import ( + ArgumentParser, + none_or_bool_or_str, + bool_sh, + joinstrs, + safe_npload, +) +from selfisys.global_parameters import * +from selfisys.utils.tools import get_k_max +from selfisys.hiddenbox import HiddenBox +from selfisys.utils.logger import getCustomLogger, INDENT, UNINDENT + +logger = getCustomLogger(__name__) + + +def worker_fct(params): + """ + Run a simulation in parallel to linearise the HiddenBox. + + Parameters + ---------- + params : tuple + A tuple containing (x, index, selfi_object): + x : int or float + Direction index (1..S) or 0 for the expansion point. + index : int or None + Simulation index for the expansion point. Expect None if + direction x is not 0. + selfi_object : object + Instance of the selfi object. + + Returns + ------- + int + Returns 0 on successful completion. + """ + from io import BytesIO + from selfisys.utils.low_level import stdout_redirector + import gc + + x, idx, selfi_object = params + logger.debug("Running simulation: x=%s, idx=%s", x, idx) + + # Capture output to avoid cluttering logs + f = BytesIO() + with stdout_redirector(f): + selfi_object.run_simulations(d=x, p=idx) + f.close() + + # Release memory + del selfi_object + gc.collect() + return 0 + + +parser = ArgumentParser( + description=( + "Step 0e of the SelfiSys pipeline. " + "Generate all the required Simbelmyne parameter files for the simulations " + "at the expansion point, and compute the prior on the initial spectrum." + ) +) +parser.add_argument("--wd", type=str, help="Absolute path of the working directory.") +parser.add_argument( + "--N_THREADS", + type=int, + default=64, + help=( + "Number of threads for computing the prior. Also serves as the number of " + "parameter files to generate in parallel (note that a distinct HiddenBox " + "object is instantiated for each)." + ), +) +parser.add_argument( + "--prior", + type=str, + default="planck2018", + help=( + "Prior for the parameters. Possible values: " + '"selfi2019" (as in [leclercq2019primordial]), ' + '"planck2018" (Planck 2018 cosmology), ' + '"planck2018_cv" (Planck 2018 + cosmic variance).' + ), +) +parser.add_argument( + "--nsamples_prior", + type=int, + default=int(1e4), + help=( + "Number of samples (drawn from the prior on cosmology) to compute the prior " + "on the primordial power spectrum (when using planck2018[_cv])." + ), +) +parser.add_argument( + "--survey_mask_path", + type=none_or_bool_or_str, + default=None, + help="Path to the survey mask for the well-specified model.", +) +parser.add_argument( + "--name_obs", + type=none_or_bool_or_str, + default=None, + help=( + "Prefix for the observation files. If None, uses a default name. " + "Can be used to work with different data vectors." + ), +) +parser.add_argument( + "--effective_volume", + type=bool_sh, + default=False, + help="Use the effective volume to compute alpha_cv.", +) +parser.add_argument( + "--force_recompute_prior", + type=bool_sh, + default=False, + help="Force overwriting the prior.", +) +parser.add_argument( + "--Ne", + type=int, + default=None, + help=( + "Number of simulations to keep at the expansion point. " + "If None, uses the value from the prior steps." + ), +) +parser.add_argument( + "--Ns", + type=int, + default=None, + help=( + "Number of simulations for each gradient component. " + "If None, uses the value from the prior steps." + ), +) + +args = parser.parse_args() + +wd = args.wd +N_THREADS = args.N_THREADS +prior_type = args.prior +nsamples_prior = int(args.nsamples_prior) +survey_mask_path = args.survey_mask_path +name_obs = "_" + args.name_obs if args.name_obs is not None else None +local_mask_prefix = args.name_obs if args.name_obs is not None else None +effective_volume = args.effective_volume +force_recompute_prior = args.force_recompute_prior + +modeldir = wd + "model/" +prior_dir = ROOT_PATH + "data/stored_priors/" +Path(prior_dir).mkdir(parents=True, exist_ok=True) + +P_0 = np.load(modeldir + "P_0.npy") + + +def theta2P(theta): + """ + Convert dimensionless theta to physical P(k). + + Parameters + ---------- + theta : ndarray + The dimensionless power-spectrum values. + + Returns + ------- + ndarray + The physical power-spectrum values. + """ + return theta * P_0 + + +if __name__ == "__main__": + from pysbmy.timestepping import read_timestepping + from os.path import exists + from selfisys.hiddenbox import HiddenBox + + try: + logger.diagnostic("Setting up main parameters...") + + with open(modeldir + "other_params.pkl", "rb") as f: + other_params = pickle.load(f) + size = other_params["size"] + Np0 = other_params["Np0"] + Npm0 = other_params["Npm0"] + L = other_params["L"] + S = other_params["S"] + total_steps = other_params["total_steps"] + aa = other_params["aa"] + P = other_params["P"] + G_sim_path = other_params["G_sim_path"] + G_ss_path = other_params["G_ss_path"] + P_ss_obj_path = other_params["P_ss_obj_path"] + Ne = other_params["Ne"] if args.Ne is None else args.Ne + Ns = other_params["Ns"] if args.Ns is None else args.Ns + Delta_theta = other_params["Delta_theta"] + sim_params = other_params["sim_params"] + + isstd = sim_params[:3] == "std" + splitLPT = sim_params[:8] == "splitLPT" + gravity_on = sim_params[:6] != "nograv" + + radial_selection = safe_npload(modeldir + "radial_selection.npy") + selection_params = np.load(modeldir + "selection_params.npy") + lin_bias = np.load(modeldir + "lin_bias.npy") + Npop = len(lin_bias) if isinstance(lin_bias, np.ndarray) else 1 + obs_density = safe_npload(modeldir + "obs_density.npy") + noise = np.load(modeldir + "noise.npy") + + k_max = get_k_max(L, size) # k_max in h/Mpc + + Pbins_bnd = np.load(modeldir + "Pbins_bnd.npy") + Pbins = np.load(modeldir + "Pbins.npy") + k_s = np.load(modeldir + "k_s.npy") + planck_Pk_EH = np.load(modeldir + "theta_planck.npy") + + INDENT() + if isstd: + TimeStepDistribution = None + eff_redshifts = None + TimeSteps = None + elif splitLPT: + TimeStepDistribution = None + TimeSteps = [f"pop{i}" for i in range(1, len(aa))] + eff_redshifts = [1 / a - 1 for a in aa[1:]] + else: + logger.info("Setting up the time-stepping...") + nsteps = [ + round((aa[i + 1] - aa[i]) / (aa[-1] - aa[0]) * total_steps) + for i in range(len(aa) - 1) + ] + if sum(nsteps) != total_steps: + nsteps[nsteps.index(max(nsteps))] += total_steps - sum(nsteps) + TimeSteps = list(np.cumsum(nsteps) - 1) + merged_path = modeldir + "merged.h5" + TS_merged = read_timestepping(merged_path) + + if sim_params.startswith("custom") or sim_params.startswith("nograv"): + TimeStepDistribution = merged_path + eff_redshifts = 1 / aa[-1] - 1 + else: + raise NotImplementedError("Time-stepping strategy not yet implemented.") + logger.info("Time-stepping setup done.") + UNINDENT() + logger.diagnostic("Setting up main parameters done.") + + # Normalisation constants + logger.diagnostic("Loading normalisation constants...") + norm_csts_path = modeldir + "norm_csts.npy" + if not exists(norm_csts_path): + raise ValueError( + "Normalisation constants not found. Please run steps 0c and 0d before 0e." + ) + norm_csts = np.load(norm_csts_path) + logger.diagnostic("Normalisation constants loaded.") + + logger.info("Instantiating the HiddenBox...") + HB_selfi = HiddenBox( + k_s=k_s, + P_ss_path=P_ss_obj_path, + Pbins_bnd=Pbins_bnd, + theta2P=theta2P, + P=P * Npop, + size=size, + L=L, + G_sim_path=G_sim_path, + G_ss_path=G_ss_path, + Np0=Np0, + Npm0=Npm0, + fsimdir=wd[:-1], + noise_std=noise, + radial_selection=radial_selection, + selection_params=selection_params, + observed_density=obs_density, + linear_bias=lin_bias, + norm_csts=norm_csts, + survey_mask_path=survey_mask_path, + local_mask_prefix=local_mask_prefix, + sim_params=sim_params, + TimeStepDistribution=TimeStepDistribution, + TimeSteps=TimeSteps, + eff_redshifts=eff_redshifts, + seedphase=BASELINE_SEEDPHASE, + seednoise=BASELINE_SEEDNOISE, + fixnoise=False, + seednorm=BASELINE_SEEDNORM, + reset=False, + save_frequency=5, + ) + logger.info("HiddenBox instantiated successfully.") + + logger.diagnostic("Loading the ground truth spectrum...") + if not exists(modeldir + "theta_gt.npy"): + raise ValueError("Ground truth cosmology not found.") + theta_gt = np.load(modeldir + "theta_gt.npy") + logger.diagnostic("Ground truth spectrum loaded.") + + logger.diagnostic("Loading observations...") + if not exists(joinstrs([modeldir, "phi_obs", name_obs, ".npy"])): + raise ValueError("Observation data not found.") + phi_obs = np.load(joinstrs([modeldir, "phi_obs", name_obs, ".npy"])) + logger.diagnostic("Observations loaded.") + + logger.info("Setting up the prior and instantiating the selfi object...") + fname_results = wd + "RESULTS/res.h5" + pool_prefix = wd + "pool/pool_res_dir_" + pool_suffix = ".h5" + + from pyselfi.power_spectrum.selfi import power_spectrum_selfi + + if prior_type == "selfi2019": + from pyselfi.power_spectrum.prior import power_spectrum_prior + + theta_0 = np.ones(S) + if effective_volume: + alpha_cv = np.load(modeldir + "alpha_cv_eff.npy") + else: + alpha_cv = np.load(modeldir + "alpha_cv.npy") + + prior = power_spectrum_prior( + k_s, theta_0, THETA_NORM_GUESS, K_CORR_GUESS, alpha_cv, False + ) + selfi = power_spectrum_selfi( + fname_results, + pool_prefix, + pool_suffix, + prior, + HB_selfi, + theta_0, + Ne, + Ns, + Delta_theta, + phi_obs, + ) + + selfi.prior.theta_norm = THETA_NORM_GUESS + selfi.prior.k_corr = K_CORR_GUESS + selfi.prior.alpha_cv = alpha_cv + elif prior_type.startswith("planck2018"): + from selfisys.prior import planck_prior + + theta_planck = np.load(modeldir + "theta_planck.npy") + theta_0 = theta_planck / P_0 + + prior = planck_prior( + planck_mean, + planck_cov, + k_s, + P_0, + k_max, + nsamples=nsamples_prior, + nthreads=N_THREADS, + filename=( + prior_dir + + f"planck_prior_S{S}_L{L}_size{size}_" + + f"{nsamples_prior}_{WHICH_SPECTRUM}.npy" + ), + ) + selfi = power_spectrum_selfi( + fname_results, + pool_prefix, + pool_suffix, + prior, + HB_selfi, + theta_0, + Ne, + Ns, + Delta_theta, + phi_obs, + ) + else: + raise ValueError(f"Unknown prior type: {prior_type}") + + logger.info("Prior and selfi object created successfully.") + + # Plot the observed summaries + logger.info("Plotting the observed summaries...") + import matplotlib.pyplot as plt + + fig, ax1 = plt.subplots(figsize=(15, 5)) + ax1.plot(k_s, theta_gt / P_0, label=r"$\theta_{\mathrm{gt}}$", color="C0") + ax1.set_xscale("log") + ax1.semilogx( + k_s, + planck_Pk_EH / P_0, + label=r"$P_{\mathrm{Planck}}(k)/P_0(k)$", + color="C1", + lw=0.5, + ) + ax1.set_xlabel("$k$ [$h$/Mpc]") + ax1.set_ylabel("$[\\mathrm{Mpc}/h]^3$") + ax1.grid(which="both", axis="y", linestyle="dotted", linewidth=0.6) + for kk in k_s[:-1]: + ax1.axvline(x=kk, color="green", linestyle="dotted", linewidth=0.6) + ax1.axvline( + x=k_s[-1], + color="green", + linestyle="dotted", + linewidth=0.6, + label=r"$\theta$-bins boundaries", + ) + ax1.axvline(x=Pbins[0], color="red", linestyle="dashed", linewidth=0.5) + ax1.axvline(x=Pbins[-1], color="red", linestyle="dashed", linewidth=0.5) + for kk in Pbins[1:-2]: + ax1.axvline(x=kk, ymax=0.167, color="red", linestyle="dashed", linewidth=0.5) + ax1.legend(loc=2) + ax1.set_xlim(max(1e-4, k_s.min() - 2e-4), k_s.max()) + ax1.set_ylim(7e-1, 1.6e0) + + ax2 = ax1.twinx() + ax2.axvline( + x=Pbins[-2], + ymax=0.333, + color="red", + linestyle="dashed", + linewidth=0.5, + label=r"$\psi$-bins centers", + ) + len_obs = len(phi_obs) // np.shape(selection_params)[1] + cols = ["C4", "C5", "C6", "C7"] + for i in range(np.shape(selection_params)[1]): + ax2.plot( + Pbins, + phi_obs[i * len_obs : (i + 1) * len_obs], + marker="x", + label=rf"Summary $\psi_{{\mathrm{{obs}}}},$ pop {i}", + linewidth=0.5, + color=cols[i % len(cols)], + ) + ax2.legend(loc=1) + ax2.set_ylabel("Summary values") + plt.title( + "Observations generated with the ground truth cosmology and well-specified models" + ) + plt.savefig(wd + "Figures/summary_obs_step0e.pdf", bbox_inches="tight", dpi=300) + plt.close() + logger.info("Plotting the observed summaries done.") + + logger.info("Loading or computing prior...") + error_str_prior = ( + "Error while computing the prior. For OOM issues, a fix might be to set " + "os.environ['OMP_NUM_THREADS'] = '1'. Otherwise, refer to the error message." + ) + + if not prior_type.startswith("selfi2019"): + if not force_recompute_prior: + try: + selfi.prior = selfi.prior.load(selfi.fname) + logger.info("Prior loaded from file.") + except: + logger.info("Prior not found in %s, recomputing...", selfi.fname) + try: + selfi.compute_prior() + selfi.save_prior() + selfi.prior = selfi.prior.load(selfi.fname) + except: + logger.critical(error_str_prior) + raise RuntimeError("Prior computation failed.") + logger.info("Prior computed and saved.") + else: + logger.info("Forcing recomputation of the prior (user request).") + selfi.compute_prior() + selfi.save_prior() + selfi.prior = selfi.prior.load(selfi.fname) + else: + selfi.compute_prior() + selfi.save_prior() + selfi.load_prior() + + from os import cpu_count + import tqdm.auto as tqdm + from multiprocessing import Pool + + HB_selfi.switch_recompute_pool() + if gravity_on: + HB_selfi.switch_setup() + + list_part_1 = [[0, idx, selfi] for idx in range(Ne)] + list_part_2 = [[x, None, selfi] for x in range(1, S + 1)] + + ncors = cpu_count() + nprocess = min(N_THREADS, ncors, len(list_part_1[1:]) + len(list_part_2)) + logger.info("Using %d processes to generate Simbelmynë parameter files.", nprocess) + gc.collect() + + # Generate parameter files for estimating f0 + logger.info("Generating parameter files for estimating f0...") + # First poke the HiddenBox once to avoid Pool access issues + worker_fct(list_part_1[0]) + with Pool(processes=nprocess) as mp_pool: + pool_results_1 = mp_pool.map(worker_fct, list_part_1[1:]) + for _ in tqdm.tqdm(pool_results_1, total=len(list_part_1[1:])): + pass + logger.info("Generating parameter files for the estimation of f0 done.") + + # Generate parameter files for estimating the gradient + logger.info("Generating parameter files for the gradient...") + with Pool(processes=nprocess) as mp_pool: + pool_results_2 = mp_pool.map(worker_fct, list_part_2) + for _ in tqdm.tqdm(pool_results_2, total=len(list_part_2)): + pass + logger.info("Generating parameter files for the gradient done.") + + if gravity_on: + HB_selfi.switch_setup() + HB_selfi.switch_recompute_pool() + + except OSError as e: + logger.error("File or directory access error during Step 0e: %s", str(e)) + raise + except Exception as e: + logger.critical("Unexpected error occurred in Step 0e: %s", str(e)) + raise RuntimeError("Step 0e failed.") from e + finally: + gc.collect() + logger.info("Step 0e of the SelfiSys pipeline: done.") diff --git a/src/selfisys/pipeline/step1_2.py b/src/selfisys/pipeline/step1_2.py new file mode 100644 index 0000000..4059a5e --- /dev/null +++ b/src/selfisys/pipeline/step1_2.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Steps 1 and 2 of the SelfiSys pipeline. + +Run all the Simbelmynë simulations needed to linearise the HiddenBox, +using the .sbmy files generated in step 0. It can run sequentially or in +parallel, depending on the --npar argument. + +Raises +------ +OSError + If file or directory paths are inaccessible. +RuntimeError + If unexpected HPC or PySbmy issues occur. +""" + +import os +import gc +import numpy as np + +from selfisys.utils.parser import ArgumentParser, none_or_bool_or_str, bool_sh +from selfisys.utils.logger import getCustomLogger + + +logger = getCustomLogger(__name__) + + +parser = ArgumentParser( + description="Run the Simbelmynë simulations required to linearise the HiddenBox." +) +parser.add_argument("--pool_path", type=str, help="Path to the pool of simulations.") +parser.add_argument("--directions", type=int, nargs="+", help="List of directions.") +parser.add_argument("--pp", type=int, nargs="+", help="List of simulation indices p.") +parser.add_argument("--Npop", type=int, help="Number of populations.", default=None) +parser.add_argument("--npar", type=int, help="Number of sim to run in parallel.", default=8) +parser.add_argument( + "--sim_params", + type=none_or_bool_or_str, + default=None, + help="Parameters for the simulations, e.g., 'splitLPT', 'custom19COLA20' etc.", +) +parser.add_argument("--force", type=bool_sh, help="Force computations.", default=False) + +args = parser.parse_args() +pool_path = args.pool_path +sim_params = args.sim_params +splitLPT = sim_params[:8] == "splitLPT" if sim_params is not None else False +force = args.force +directions = np.array(args.directions, dtype=int) +pp = np.array(args.pp, dtype=int) +Npop = args.Npop +npar = args.npar + + +def run_sim(val): + """ + Execute a single Simbelmynë simulation. + + Parameters + ---------- + val : tuple + A tuple (d, p, ipop) containing: + d : int + Direction index. + p : int + Simulation index. + ipop : str or None + Population identifier for splitLPT (e.g. 'pop0', 'pop1', + etc), None for other approaches. + + Raises + ------ + OSError + If the .sbmy file or output path is invalid. + RuntimeError + If the simulation fails unexpectedly. + """ + from pysbmy import pySbmy + + d, p, ipop = val + dirpath = f"{pool_path}d{d}/" + if ipop is not None: + fname_simparfile = f"{dirpath}sim_d{d}_p{p}_{ipop}.sbmy" + else: + fname_simparfile = f"{dirpath}sim_d{d}_p{p}.sbmy" + fname_output = f"{dirpath}output_density_d{d}_p{p}.h5" + fname_simlogs = f"{dirpath}logs_sim_d{d}_p{p}.txt" + + if os.path.isfile(fname_output) and not force: + logger.info("Output file %s already exists, skipping...", fname_output) + gc.collect() + else: + from io import BytesIO + from selfisys.utils.low_level import stdout_redirector, stderr_redirector + + logger.debug("Running Simbelmynë for d=%d, p=%d, ipop=%s", d, p, ipop) + # sys.stdout.flush() + f = BytesIO() + g = BytesIO() + with stdout_redirector(f): + with stderr_redirector(g): + pySbmy(fname_simparfile, fname_simlogs) + g.close() + f.close() + # sys.stdout.flush() + gc.collect() + logger.debug("Simbelmynë run completed for d=%d, p=%d, ipop=%s", d, p, ipop) + + +if len(pp) == 1 and pp[0] == -1: + # If simulation indices are not specified, find them in the + # pool_path directory + if splitLPT: + raise ValueError("pp = -1 not supported with splitLPT.") + + pp = np.array( + [ + int(f.split("_")[2].split(".")[0][1:]) + for f in os.listdir(f"{pool_path}d{directions[0]}") + if f.startswith("sim_d") and f.endswith(".sbmy") + ], + dtype=int, + ) + + +if __name__ == "__main__": + import tqdm.auto as tqdm + from itertools import product + + try: + if splitLPT: + if Npop is None: + raise ValueError("Npop must be specified when using splitLPT mode.") + pops = [f"pop{i}" for i in range(Npop)] + vals = list(product(directions, pp, pops)) + else: + vals = list(product(directions, pp, [Npop])) + + nsim = len(vals) + logger.info("Found %d simulation tasks to run.", nsim) + + if npar > 1: + from multiprocessing import Pool + + logger.info("Running simulations using %d processes in parallel.", npar) + with Pool(processes=npar) as mp_pool: + for _ in tqdm.tqdm(mp_pool.imap(run_sim, vals), total=nsim): + pass + logger.info("Running simulations done.") + else: + logger.info("Running simulations sequentially...") + for _ in tqdm.tqdm(map(run_sim, vals), total=nsim): + pass + logger.info("Running simulations done.") + + except OSError as e: + logger.error("File or directory access error: %s", str(e)) + raise + except Exception as e: + logger.critical("Unexpected error in step X: %s", str(e)) + raise RuntimeError("Simulations failed.") from e + finally: + gc.collect() + logger.info("All simulations completed successfully.") diff --git a/src/selfisys/pipeline/step3.py b/src/selfisys/pipeline/step3.py new file mode 100644 index 0000000..da7449a --- /dev/null +++ b/src/selfisys/pipeline/step3.py @@ -0,0 +1,1187 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Third step of the SelfiSys pipeline. + +Run the initial matter power spectrum inference, using the simulations +performed in previous steps. + +Raises +------ +OSError + If file or directory paths are inaccessible. +RuntimeError + If unexpected HPC or PySbmy issues occur. +""" + +import gc + +from selfisys.utils.parser import ( + ArgumentParser, + none_or_bool_or_str, + bool_sh, + joinstrs, + safe_npload, +) +from selfisys.utils.logger import getCustomLogger, INDENT, UNINDENT + +logger = getCustomLogger(__name__) + + +def worker_fct(params): + """ + Run a Simbelmynë simulation. + + Parameters + ---------- + params : tuple + (x, index, selfi_object) + x : int or float + Direction index (1..S), or 0 for the expansion point. + index : int or None + Simulation index for the expansion point. + selfi_object : object + Instance of the selfi object. + + Returns + ------- + int + Returns 0 on successful completion. + + Raises + ------ + OSError + If file/directory access fails for .sbmy or logs. + RuntimeError + If the simulation fails unexpectedly. + """ + from io import BytesIO + from selfisys.utils.low_level import stdout_redirector, stderr_redirector + + x, index, selfi_object = params + + # Check consistency + if x != 0 and index is not None: + raise ValueError("Expansion point is not 0 but index is not None.") + + logger.debug("Running simulation: offset=%s, index=%s", x, index) + + # Suppress console output to keep logs clean. Use with caution. + f = BytesIO() + g = BytesIO() + with stdout_redirector(f): + with stderr_redirector(g): + selfi_object.run_simulations(d=x, p=index) + g.close() + f.close() + + del selfi_object + gc.collect() + return 0 + + +from pathlib import Path +from os.path import exists +import numpy as np + +from selfisys.global_parameters import * +from selfisys.utils.tools import get_k_max + +parser = ArgumentParser( + description=( + "Third step of the SelfiSys pipeline. Run the initial matter power spectrum inference." + ) +) +parser.add_argument("--wd", type=str, help="Absolute path of the working directory.") +parser.add_argument("--N_THREADS", type=int, help="1 direction per thread.", default=64) +parser.add_argument( + "--N_THREADS_PRIOR", type=int, help="Number of threads for computing the prior.", default=64 +) +parser.add_argument( + "--prior", + type=str, + default="planck2018", + help=( + "Prior for the parameters. Possible values:\n" + ' - "selfi2019": prior used in [leclercq2019primordial]\n' + ' - "planck2018": sampling from Planck 2018 cosmology\n' + ' - "planck2018_cv": Planck 2018 + cosmic variance.\n' + "Note: 'selfi2019' and 'planck2018_cv' have not been checked with the latest code " + "version. Use at your own risk." + ), +) +parser.add_argument( + "--nsamples_prior", + type=int, + default=int(1e4), + help="Number of samples to compute planck2018[_cv].", +) +parser.add_argument( + "--survey_mask_path", + type=none_or_bool_or_str, + default=None, + help="Path to the survey mask.", +) +parser.add_argument( + "--effective_volume", + type=bool_sh, + default=False, + help="Use the effective volume to compute alpha_cv.", +) +parser.add_argument( + "--name_obs", + type=none_or_bool_or_str, + default=None, + help=( + "Prefix for the observation file. If None, uses default name. " + "Enables working with different data vectors." + ), +) +parser.add_argument( + "--params_obs", + type=none_or_bool_or_str, + default=None, + help="Recompute observations with the specified parameters.", +) +parser.add_argument( + "--force_obs", + type=bool_sh, + default=False, + help="Force re-computation of the observations.", +) +parser.add_argument( + "--recompute_obs_mock", + type=bool_sh, + default=False, + help="Recompute the observational part of the data vector.", +) +parser.add_argument( + "--time_steps_obs", + type=int, + nargs="*", + default=None, + help=( + "Suffixes of the fields to use for recomputing the observational part of the data vector. " + "Ignored if `recompute_obs_mock=False`." + ), +) +parser.add_argument( + "--force_recompute_prior", + type=bool_sh, + default=False, + help="Force overwriting the prior.", +) +parser.add_argument( + "--update_obs_phase", + type=bool_sh, + default=False, + help="Change the phase for observations.", +) +parser.add_argument( + "--recompute_mocks", + type=none_or_bool_or_str, + default=False, + help=( + "Recompute all mocks in the inference phase, without affecting the dark matter fields. " + "Possible values: True, 'gradients', 'list', 'listdd', 'list_exp'." + ), +) +parser.add_argument( + "--list_of_pps", + type=float, + nargs="*", + default=None, + help="Indices to recompute mocks for gradient, if 'list' is chosen.", +) +parser.add_argument("--pp_min", type=int, help="Min index for recompute_mocks in gradient mode.") +parser.add_argument("--pp_max", type=int, help="Max index for recompute_mocks in gradient mode.") +parser.add_argument("--dd_min", type=int, help="Min index for recompute_mocks in listdd mode.") +parser.add_argument("--dd_max", type=int, help="Max index for recompute_mocks in listdd mode.") +parser.add_argument( + "--perform_score_compression", + type=bool_sh, + default=False, + help="Perform score compression stage.", +) +parser.add_argument( + "--force_score_compression", + type=bool_sh, + default=False, + help="Force re-computation of the score compression.", +) +parser.add_argument( + "--test_gravity", + type=bool_sh, + default=False, + help="Flag to test gravity parameters. If True, norm_csts_path must be given.", +) +parser.add_argument( + "--neglect_lightcone", + type=bool_sh, + default=False, + help="Neglect lightcone effects even if multiple snapshots are available.", +) +parser.add_argument( + "--norm_csts_path", + type=none_or_bool_or_str, + default=None, + help="Path to external normalisation constants (needed for test_gravity=True).", +) +parser.add_argument( + "--Ne", + type=int, + default=None, + help="Number of simulations at the expansion point (override if not None).", +) +parser.add_argument( + "--Ns", + type=int, + default=None, + help="Number of simulations per gradient component (override if not None).", +) +parser.add_argument( + "--prefix_mocks", + type=none_or_bool_or_str, + default=None, + help="Prefix for mock files, if any.", +) +parser.add_argument( + "--selection_params", + type=float, + nargs="*", + default=None, + help="Selection function parameters for the well-specified model.", +) +parser.add_argument( + "--reset_window_function", + type=bool_sh, + default=False, + help="Reset the window function if True.", +) +parser.add_argument( + "--obs_density", + type=float, + default=None, + help="Observed density override for the well-specified model.", +) +parser.add_argument( + "--lin_bias", + type=float, + nargs="*", + default=None, + help="Linear bias override; uses stored value otherwise.", +) +parser.add_argument( + "--figdir_suffix", + type=none_or_bool_or_str, + default=None, + help="Suffix for the figures directory.", +) +parser.add_argument( + "--noise_dbg", + type=float, + default=None, + help="Manually specify noise level (for debugging). " + "Normalisation constants won't reflect this override: use with caution.", +) + +args = parser.parse_args() + +wd = args.wd +N_THREADS = args.N_THREADS +N_THREADS_PRIOR = args.N_THREADS_PRIOR +prior_type = args.prior +nsamples_prior = int(args.nsamples_prior) +survey_mask_path = args.survey_mask_path +effective_volume = args.effective_volume +force_recompute_prior = args.force_recompute_prior +update_obs_phase = args.update_obs_phase +recompute_mocks = args.recompute_mocks +list_of_pps = args.list_of_pps +pp_min = args.pp_min +pp_max = args.pp_max +ddmin = args.dd_min +ddmax = args.dd_max +prefix_mocks = args.prefix_mocks +params_obs = args.params_obs +name_obs = "_" + args.name_obs if args.name_obs is not None else None +force_obs = args.force_obs +recompute_obs_mock = args.recompute_obs_mock if not force_obs else True +time_steps_obs = args.time_steps_obs +local_mask_prefix = args.name_obs if args.name_obs is not None else None +reset_window_function = args.reset_window_function +perform_score_compression = args.perform_score_compression +force_score_compression = args.force_score_compression +test_gravity = args.test_gravity +neglect_lightcone = args.neglect_lightcone +norm_csts_path = args.norm_csts_path +figdir_suffix = args.figdir_suffix + "/" if args.figdir_suffix is not None else "" +noise_dbg = args.noise_dbg + +# Consistency checks +if pp_min is not None or pp_max is not None: + if pp_min is None or pp_max is None: + raise ValueError("both pp_min and pp_max should be specified if one of them is.") + elif list_of_pps is not None: + raise ValueError("pp_min and pp_max should not be specified if list_of_pps is specified.") + else: + list_of_pps = range(pp_min, pp_max + 1) + +if list_of_pps is not None and recompute_mocks: + if recompute_mocks[:4] != "list": + raise ValueError("To use list_of_pps, set recompute_mocks to 'list'.") + +if test_gravity: + if force_obs: + raise ValueError("test_gravity and force_obs cannot both be True.") + if norm_csts_path is None: + raise ValueError("norm_csts_path should be specified if test_gravity is True.") + +if time_steps_obs is not None and not recompute_obs_mock: + raise ValueError( + "time_steps_obs can't be specified if recompute_obs_mock is False. " + "If you want to recompute observations, set recompute_obs_mock=True." + ) + +# Load parameters +modeldir = wd + "model/" +if prefix_mocks is not None: + modeldir_refined = modeldir + prefix_mocks + "/" +else: + modeldir_refined = modeldir +if args.selection_params is None: + selection_params = np.load(modeldir + "selection_params.npy") +else: + selection_params = np.reshape(np.array(args.selection_params), (3, -1)) + +if args.norm_csts_path is None: + norm_csts_path = modeldir + "norm_csts.npy" + +radial_selection = safe_npload(modeldir + "radial_selection.npy") + +# If user hasn't specified new obs_density, load it from file +if args.obs_density is None: + obs_density = safe_npload(modeldir + "obs_density.npy") +else: + obs_density = args.obs_density + +# If user hasn't specified new linear bias, load it from file +if args.lin_bias is None: + lin_bias = np.load(modeldir + "lin_bias.npy") +else: + if not (recompute_mocks or force_obs): + raise ValueError( + "lin_bias shouldn't be specified if neither recompute_mocks nor force_obs is True." + ) + lin_bias = args.lin_bias + if not isinstance(lin_bias, float): + lin_bias = np.array(args.lin_bias) + +Npop = len(lin_bias) if isinstance(lin_bias, np.ndarray) else 1 + +Path(wd + "Figures/").mkdir(exist_ok=True) +modeldir_obs = wd + "model/" + +if prefix_mocks is not None: + resultsdir = wd + "RESULTS/" + prefix_mocks + "/" + figuresdir = joinstrs([wd, "Figures/", prefix_mocks, "/", figdir_suffix]) + resultsdir_obs = wd + "RESULTS/" + prefix_mocks + "/" + scoredir = wd + "score_compression/" + prefix_mocks + "/" + fname_results = wd + "RESULTS/" + prefix_mocks + "/res.h5" +else: + resultsdir = wd + "RESULTS/" + figuresdir = joinstrs([wd, "Figures/", figdir_suffix]) + resultsdir_obs = wd + "RESULTS/" + scoredir = wd + "score_compression/" + fname_results = wd + "RESULTS/res.h5" + +Path(resultsdir).mkdir(parents=True, exist_ok=True) +Path(figuresdir).mkdir(parents=True, exist_ok=True) +Path(resultsdir_obs).mkdir(parents=True, exist_ok=True) +Path(modeldir_obs).mkdir(parents=True, exist_ok=True) +Path(modeldir_refined).mkdir(parents=True, exist_ok=True) +Path(scoredir).mkdir(parents=True, exist_ok=True) + +logger.diagnostic("> Loading normalisation constants from: %s", norm_csts_path) +if not exists(norm_csts_path): + raise ValueError("Normalisation constants not found. Please run steps 0, 1, 2 before step 3.") +else: + norm_csts = np.load(norm_csts_path) +logger.diagnostic("Normalisation constants loaded successfully.") + +# Write all parameters to a text file for reference +with open(figuresdir + "parameters.txt", "w") as f: + f.write("wd = " + wd + "\n") + f.write("N_THREADS = " + str(N_THREADS) + "\n") + f.write("N_THREADS_PRIOR = " + str(N_THREADS_PRIOR) + "\n") + f.write("prior_type = " + prior_type + "\n") + f.write("nsamples_prior = " + str(nsamples_prior) + "\n") + f.write("survey_mask_path = " + str(survey_mask_path) + "\n") + f.write("effective_volume = " + str(effective_volume) + "\n") + f.write("force_recompute_prior = " + str(force_recompute_prior) + "\n") + f.write("update_obs_phase = " + str(update_obs_phase) + "\n") + f.write("recompute_mocks = " + str(recompute_mocks) + "\n") + f.write("list_of_pps = " + str(list_of_pps) + "\n") + f.write("pp_min = " + str(pp_min) + "\n") + f.write("pp_max = " + str(pp_max) + "\n") + f.write("dd_min = " + str(ddmin) + "\n") + f.write("dd_max = " + str(ddmax) + "\n") + f.write("prefix_mocks = " + str(prefix_mocks) + "\n") + f.write("params_obs = " + str(params_obs) + "\n") + f.write("name_obs = " + str(name_obs) + "\n") + f.write("force_obs = " + str(force_obs) + "\n") + f.write("recompute_obs_mock = " + str(recompute_obs_mock) + "\n") + f.write("local_mask_prefix = " + str(local_mask_prefix) + "\n") + f.write("reset_window_function = " + str(reset_window_function) + "\n") + f.write("perform_score_compression = " + str(perform_score_compression) + "\n") + f.write("force_score_compression = " + str(force_score_compression) + "\n") + f.write("test_gravity = " + str(test_gravity) + "\n") + f.write("norm_csts_path = " + str(norm_csts_path) + "\n") + f.write("norm_csts = " + str(norm_csts) + "\n") + f.write("noise_dbg = " + str(noise_dbg) + "\n") + +P_0 = np.load(modeldir + "P_0.npy") + + +def theta2P(theta): + """ + Convert dimensionless theta to physical P(k). + + Parameters + ---------- + theta : ndarray + Dimensionless power-spectrum values. + + Returns + ------- + ndarray + Physical power-spectrum values (P_0 multiplied). + """ + return theta * P_0 + + +if __name__ == "__main__": + import gc + from pickle import load + from pysbmy.timestepping import read_timestepping + from selfisys.hiddenbox import HiddenBox + + try: + logger.diagnostic( + "Loading main parameters from other_params.pkl in modeldir: %s", modeldir + ) + with open(modeldir + "other_params.pkl", "rb") as f: + other_params = load(f) + size = other_params["size"] + Np0 = other_params["Np0"] + Npm0 = other_params["Npm0"] + L = other_params["L"] + S = other_params["S"] + total_steps = other_params["total_steps"] + aa = other_params["aa"] + P = other_params["P"] + G_sim_path = other_params["G_sim_path"] + G_ss_path = other_params["G_ss_path"] + P_ss_obj_path = other_params["P_ss_obj_path"] + Pinit = other_params["Pinit"] + Ne = other_params["Ne"] if args.Ne is None else args.Ne + Ns = other_params["Ns"] if args.Ns is None else args.Ns + Delta_theta = other_params["Delta_theta"] + sim_params = other_params["sim_params"] + isstd = sim_params[:3] == "std" + splitLPT = sim_params[:8] == "splitLPT" + + noise = np.load(modeldir + "noise.npy") if noise_dbg is None else noise_dbg + + k_max = get_k_max(L, size) # k_max in h/Mpc + + params_planck_EH = params_planck_kmax_missing.copy() + params_planck_EH["k_max"] = k_max + params_cosmo_obs = params_cosmo_obs_kmax_missing.copy() + params_cosmo_obs["k_max"] = k_max + + Pbins_bnd = np.load(modeldir + "Pbins_bnd.npy") + Pbins = np.load(modeldir + "Pbins.npy") + k_s = np.load(modeldir + "k_s.npy") + planck_Pk = np.load(modeldir + "theta_planck.npy") + + logger.diagnostic("Successfully loaded input data.") + + if isstd: + TimeStepDistribution = None + eff_redshifts = None + TimeSteps = None + elif splitLPT: + TimeStepDistribution = None + TimeSteps = [f"pop{i}" for i in range(1, len(aa))] + eff_redshifts = [1 / a - 1 for a in aa[1:]] + else: + logger.info("Setting up time-stepping...") + nsteps = [ + round((aa[i + 1] - aa[i]) / (aa[-1] - aa[0]) * total_steps) + for i in range(len(aa) - 1) + ] + if sum(nsteps) != total_steps: + nsteps[nsteps.index(max(nsteps))] += total_steps - sum(nsteps) + TimeSteps = list(np.cumsum(nsteps) - 1) + merged_path = modeldir + "merged.h5" + TS_merged = read_timestepping(merged_path) + if sim_params.startswith("custom") or sim_params.startswith("nograv"): + TimeStepDistribution = merged_path + eff_redshifts = 1 / aa[-1] - 1 + else: + raise NotImplementedError("Time-stepping strategy not yet implemented.") + logger.info("Time-stepping setup done.") + + logger.info("Instantiating the HiddenBox...") + BB_selfi = HiddenBox( + k_s=k_s, + P_ss_path=P_ss_obj_path, + Pbins_bnd=Pbins_bnd, + theta2P=theta2P, + P=P * Npop, + size=size, + L=L, + G_sim_path=G_sim_path, + G_ss_path=G_ss_path, + Np0=Np0, + Npm0=Npm0, + fsimdir=wd[:-1], + modeldir=modeldir_refined, + noise_std=noise, + radial_selection=radial_selection, + selection_params=selection_params, + observed_density=obs_density, + linear_bias=lin_bias, + norm_csts=norm_csts, + survey_mask_path=survey_mask_path, + local_mask_prefix=local_mask_prefix, + sim_params=sim_params, + TimeStepDistribution=TimeStepDistribution, + TimeSteps=TimeSteps, + eff_redshifts=eff_redshifts, + seedphase=BASELINE_SEEDPHASE, + seednoise=BASELINE_SEEDNOISE, + fixnoise=False, + seednorm=BASELINE_SEEDNORM, + reset=reset_window_function, + save_frequency=5, + ) + logger.info("HiddenBox instantiated successfully.") + + modeldir_obs = wd + "model/" + if force_obs: + from pysbmy.power import get_Pk + + logger.info("Forceful re-computation of the ground truth.") + theta_gt = get_Pk(k_s, params_cosmo_obs) + np.save(joinstrs([modeldir_obs, "theta_gt", name_obs]), theta_gt) + logger.info("Ground truth recomputed successfully.") + elif not exists(joinstrs([modeldir_obs, "theta_gt", name_obs, ".npy"])): + raise ValueError("Ground truth cosmology not found. Please run prior steps.") + else: + theta_gt = np.load(joinstrs([modeldir_obs, "theta_gt", name_obs, ".npy"])) + + if ( + not exists(joinstrs([modeldir, "phi_obs", name_obs, ".npy"])) + and not recompute_obs_mock + ): + raise ValueError( + "Observations not found. Please re-run previous steps or set --recompute_obs_mock." + ) + elif recompute_obs_mock: + logger.info("Recomputing the observed data vector...") + if exists(joinstrs([modeldir_obs, "phi_obs", name_obs, ".npy"])) and not ( + update_obs_phase + ): + d_obs = -2 + else: + d_obs = -1 + if time_steps_obs is not None: + BB_selfi.update(TimeSteps=time_steps_obs) + if params_obs is not None: + BB_selfi.update(sim_params=params_obs) + + BB_selfi.switch_recompute_pool() + phi_obs = BB_selfi.make_data( + cosmo=params_cosmo_obs, + id=joinstrs([BASEID_OBS, name_obs]), + seedphase=SEEDPHASE_OBS, + seednoise=SEEDNOISE_OBS, + d=d_obs, + force_powerspectrum=force_obs, + force_parfiles=force_obs, + force_sim=force_obs, + force_cosmo=force_obs, + ) + BB_selfi.switch_recompute_pool() + + if params_obs is not None: + BB_selfi.update(sim_params=sim_params) + if time_steps_obs is not None: + BB_selfi.update(TimeSteps=TimeSteps) + np.save(joinstrs([modeldir_obs, "phi_obs", name_obs, ".npy"]), phi_obs) + logger.info("Observations recomputed successfully.") + else: + phi_obs = np.load(joinstrs([modeldir_obs, "phi_obs", name_obs, ".npy"])) + + logger.info("Setting up the prior and instantiating the selfi object...") + pool_prefix = wd + "pool/pool_res_dir_" + pool_suffix = ".h5" + from pyselfi.power_spectrum.selfi import power_spectrum_selfi + + if prior_type == "selfi2019": + from pyselfi.power_spectrum.prior import power_spectrum_prior + + theta_0 = np.ones(S) + if effective_volume: + alpha_cv = np.load(modeldir + "alpha_cv_eff.npy") + else: + alpha_cv = np.load(modeldir + "alpha_cv.npy") + + prior = power_spectrum_prior( + k_s, theta_0, THETA_NORM_GUESS, K_CORR_GUESS, alpha_cv, False + ) + selfi = power_spectrum_selfi( + fname_results, + pool_prefix, + pool_suffix, + prior, + BB_selfi, + theta_0, + Ne, + Ns, + Delta_theta, + phi_obs, + ) + selfi.prior.theta_norm = THETA_NORM_GUESS + selfi.prior.k_corr = K_CORR_GUESS + selfi.prior.alpha_cv = alpha_cv + elif prior_type.startswith("planck2018"): + from selfisys.prior import planck_prior + + theta_planck = np.load(modeldir + "theta_planck.npy") + theta_0 = theta_planck / P_0 + + prior = planck_prior( + planck_mean, + planck_cov, + k_s, + P_0, + k_max, + nsamples=nsamples_prior, + nthreads=N_THREADS_PRIOR, + filename=joinstrs( + [ + ROOT_PATH, + "data/stored_priors/planck_prior_S", + str(S), + "_L", + str(L), + "_size", + str(size), + "_", + str(nsamples_prior), + "_", + str(WHICH_SPECTRUM), + ".npy", + ] + ), + ) + selfi = power_spectrum_selfi( + fname_results, + pool_prefix, + pool_suffix, + prior, + BB_selfi, + theta_0, + Ne, + Ns, + Delta_theta, + phi_obs, + ) + else: + raise ValueError(f"Unknown prior type: {prior_type}") + logger.info("Prior set up and selfi object instantiated successfully.") + + if prior_type != "selfi2019": + logger.info("Computing / loading prior...") + if not force_recompute_prior: + try: + selfi.prior = selfi.prior.load(selfi.fname) + except: + logger.warning( + "Prior not found in %s, computing from scratch. Possible incomplete step 1?", + selfi.fname, + ) + try: + logger.info("Computing prior from scratch.") + selfi.compute_prior() + selfi.save_prior() + selfi.prior = selfi.prior.load(selfi.fname) + except: + logger.error( + "Error whilst recomputing the prior. For OOM or another error encountered while computing prior. " + "For OOM issues, a simple fix might be setting os.environ['OMP_NUM_THREADS'] = '1'. " + "Otherwise, refer the the error message." + ) + else: + try: + logger.warning("Forcefully recomputing prior from scratch: %s", selfi.fname) + selfi.compute_prior() + selfi.save_prior() + selfi.prior = selfi.prior.load(selfi.fname) + except: + logger.error( + "OOM or another error while forcing prior computation. " + "For OOM issues, a simple fix might be setting os.environ['OMP_NUM_THREADS'] = '1'. " + "Otherwise, refer the the error message." + ) + logger.info("Prior computed / loaded successfully.") + else: + selfi.compute_prior() + selfi.save_prior() + selfi.load_prior() + + if recompute_mocks: + if neglect_lightcone: + BB_selfi.update(_force_neglect_lightcone=True) + logger.info("Recomputing mocks...") + INDENT() + + from multiprocessing import Pool + from os import cpu_count + import tqdm.auto as tqdm + + if recompute_mocks is True: + list_part_1 = [[0, idx, selfi] for idx in range(Ne)] + list_part_2 = [[x, None, selfi] for x in range(1, S + 1)] + liste = list_part_1 + list_part_2 + elif recompute_mocks == "gradients": + liste = [[x, None, selfi] for x in range(1, S + 1)] + elif recompute_mocks == "list": + liste = [[x, p, selfi] for x in range(1, S + 1) for p in list_of_pps] + elif recompute_mocks == "listdd": + liste = [[d, None, selfi] for d in range(ddmin, ddmax + 1)] + elif recompute_mocks == "list_exp": + liste = [[0, p, selfi] for p in list_of_pps] + else: + raise ValueError("recompute_mocks can't be {}".format(recompute_mocks)) + + ncors = cpu_count() + nprocess = min(N_THREADS, ncors, len(liste)) + logger.info("Using %s processes to compute the mocks", nprocess) + gc.collect() + BB_selfi.switch_recompute_pool(prefix_mocks=prefix_mocks) + with Pool(processes=nprocess) as mp_pool: + import tqdm.auto as tqdm + + pool = mp_pool.imap(worker_fct, liste) + for contrib_to_grad in tqdm.tqdm(pool, total=len(liste)): + pass + BB_selfi.switch_recompute_pool(prefix_mocks=None) + UNINDENT() + logger.info("Mocks recomputed successfully.") + + logger.diagnostic("Loading simulations...") + BB_selfi.update(_prefix_mocks=prefix_mocks) + selfi.likelihood.switch_bar() + selfi.run_simulations(Ne=Ne, Ns=Ns) # > Load < the simulations + selfi.likelihood.switch_bar() + BB_selfi.update(_prefix_mocks=None) + logger.diagnostic("Simulations loaded successfully.") + + from selfisys.utils.plot_utils import * + + logger.diagnostic("Plotting the observed summaries...") + plot_observations( + k_s, + theta_gt, + planck_Pk, + P_0, + Pbins, + phi_obs, + np.shape(selection_params)[1], + path=figuresdir + f"observations_{name_obs}.png", + ) + gc.collect() + logger.diagnostic("Plotting the observed summaries done.") + + logger.info("Running the inference...") + INDENT() + logger.info("Computing likelihoods and posteriors...") + selfi.compute_likelihood() + selfi.save_likelihood() + selfi.load_likelihood() + selfi.compute_posterior() + selfi.save_posterior() + selfi.load_posterior() + logger.info("Computing likelihoods and posteriors done.") + + logger.diagnostic("Preparing SELFI outputs for plotting...") + C_0 = selfi.likelihood.C_0 + grad_f = selfi.likelihood.grad_f + Phi_0 = selfi.likelihood.Phi_0.Phi + f_0 = selfi.likelihood.f_0 + f_16 = selfi.likelihood.f_8 + f_32 = selfi.likelihood.f_16 + f_48 = selfi.likelihood.f_24 + # f_16 = selfi.likelihood.f_16 # DBG. TODO: put this back. + # f_32 = selfi.likelihood.f_32 + # f_48 = selfi.likelihood.f_48 + grad_f_16 = (f_16 - f_0) / Delta_theta + grad_f_32 = (f_32 - f_0) / Delta_theta + grad_f_48 = (f_48 - f_0) / Delta_theta + X0, Y0 = np.meshgrid(Pbins, Pbins) + X1, Y1 = np.meshgrid(k_s, Pbins) + N = Ne + + np.save(resultsdir + "Phi_0.npy", Phi_0) + np.save(resultsdir + "grad_f.npy", grad_f) + np.save(resultsdir + "f_0.npy", f_0) + np.save(resultsdir + "f_16.npy", f_16) + np.save(resultsdir + "f_32.npy", f_32) + np.save(resultsdir + "f_48.npy", f_48) + np.save(resultsdir + "C_0.npy", C_0) + logger.diagnostic("Preparing SELFI outputs for plotting done.") + + logger.diagnostic("Plotting the mocks and covariance matrices...") + CovarianceMap = create_colormap("CovarianceMap") + plot_mocks( + 1, + N, + P, + Pbins, + phi_obs, + Phi_0, + np.mean(Phi_0, axis=0), + C_0, + X0, + Y0, + CovarianceMap, + savepath=figuresdir + "covariance_matrix.png", + ) + plot_mocks_compact( + 1, + N, + P, + Pbins, + phi_obs, + Phi_0, + np.mean(Phi_0, axis=0), + C_0, + savepath=figuresdir + "mocks.pdf", + ) + logger.diagnostic("Plotting the mocks and covariance matrices done.") + + logger.diagnostic("Plotting the full covariance matrix...") + FullCovarianceMap = create_colormap("FullCovarianceMap") + plot_C( + C_0, + X0, + Y0, + Pbins, + FullCovarianceMap, + binning=False, + suptitle="Full covariance matrix", + savepath=figuresdir + "full_covariance_matrix.png", + ) + logger.diagnostic("Plotting the full covariance matrix done.") + + logger.diagnostic("Plotting the estimated gradients...") + plot_gradients( + Pbins, + P, + grad_f_16, + grad_f_32, + grad_f_48, + grad_f, + k_s, + X1, + Y1, + fixscale=True, + suptitle="Estimated gradients at expansion point for all populations of galaxies", + savepath=figuresdir + "gradients.png", + ) + logger.diagnostic("Plotting the estimated gradients done.") + + if prior_type == "selfi2019": + # Optimise the prior hyperparameters + theta_norm_min = 0.01 + theta_norm_max = 0.1 + k_corr_min = 0.005 + k_corr_max = 0.015 + + theta_norm = THETA_NORM_GUESS + k_corr = K_CORR_GUESS + # The routine `Nbin_min` in "power_spectrum/prior.py" finds + # the index of the minimal wavenumber given k_min, that is, + # minimal index such that k_s[Nbin_min] >= k_min + Nbin_min, Nbin_max = selfi.prior.Nbin_min(0), selfi.prior.Nbin_max(k_max) + + from selfisys.prior import perform_prior_optimisation_and_plot + + theta_fiducial = planck_Pk / P_0 + logger.info("Performing hyperparameters optimisation for the prior...") + theta_norm, k_corr = perform_prior_optimisation_and_plot( + selfi, + theta_fiducial, + k_opt_min=0.0, + k_opt_max=max(k_s), + theta_norm_mean=0.2, + theta_norm_std=0.3, + k_corr_mean=0.020, + k_corr_std=0.015, + theta_norm_min=theta_norm_min, + theta_norm_max=theta_norm_max, + k_corr_min=k_corr_min, + k_corr_max=k_corr_max, + meshsize=50, + Nbin_min=Nbin_min, + Nbin_max=Nbin_max, + theta_norm=theta_norm, + k_corr=k_corr, + alpha_cv=alpha_cv, + plot=True, + verbose=False, + savepath=wd + "Figures/prior_optimisation.png", + ) + logger.info("Performing hyperparameters optimisation for the prior done.") + + prior_theta_mean = selfi.prior.mean + prior_theta_covariance = selfi.prior.covariance + Nbin_min, Nbin_max = 0, len(k_s) # keep all scales + k_s = k_s[Nbin_min:Nbin_max] + P_0 = P_0[Nbin_min:Nbin_max] + prior_theta_mean = prior_theta_mean[Nbin_min:Nbin_max] + prior_theta_covariance = prior_theta_covariance[Nbin_min:Nbin_max, Nbin_min:Nbin_max] + posterior_theta_mean, posterior_theta_covariance, posterior_theta_icov = ( + selfi.restrict_posterior(Nbin_min, Nbin_max) + ) + + X2, Y2 = np.meshgrid(k_s, k_s) + prior_covariance = np.diag(P_0).dot(prior_theta_covariance).dot(np.diag(P_0)) + np.save(resultsdir + "prior_theta_mean.npy", prior_theta_mean) + np.save(resultsdir + "prior_theta_covariance.npy", prior_theta_covariance) + np.save(resultsdir_obs + "posterior_theta_mean.npy", posterior_theta_mean) + np.save(resultsdir_obs + "posterior_theta_covariance.npy", posterior_theta_covariance) + + logger.diagnostic("Plotting the prior and posterior...") + plot_prior_and_posterior_covariances( + X2, + Y2, + k_s, + prior_theta_covariance, + prior_covariance, + posterior_theta_covariance, + P_0, + suptitle="Prior and posterior covariance matrices", + savepath=figuresdir + "prior_and_posterior_covariances.png", + ) + logger.diagnostic("Plotting the prior and posterior done.") + + logger.diagnostic("Plotting the reconstruction...") + plot_reconstruction( + k_s, + Pbins, + prior_theta_mean, + prior_theta_covariance, + posterior_theta_mean, + posterior_theta_covariance, + theta_gt, + P_0, + suptitle="Posterior primordial matter power spectrum", + savepath=figuresdir + "reconstruction.png", + ) + logger.diagnostic("Plotting the reconstruction done.") + UNINDENT() + logger.info("Inference done.") + + if perform_score_compression: + logger.info("Performing score compression...") + INDENT() + + from selfisys.utils.tools import * + from selfisys.utils.workers import evaluate_gradient_of_Symbelmyne + + logger.info("Computing the gradient of CLASS wrt the cosmological parameters...") + delta = 1e-3 + if not exists(wd + "score_compression/grads_class.npy") or force_score_compression: + coeffs = [4 / 5, -1 / 5, 4 / 105, -1 / 280] + + grad = np.zeros((len(planck_mean), len(k_s))) + for i in range(len(planck_mean)): + if i == 0: # workaround to correctly evaluate the gradient with respect to h + delta *= 10 + logger.diagnostic("Evaluating gradient of CLASS wrt parameter %d" % i) + deltas_x = delta * np.linspace(1, len(coeffs), len(coeffs)) + grad[i, :] = evaluate_gradient_of_Symbelmyne( + planck_mean, + i, + k_s, + coeffs=coeffs, + deltas_x=deltas_x, + delta=delta, + kmax=max(k_s), + ) + if i == 0: + delta /= 10 + np.save(wd + "score_compression/grads_class.npy", grad) + else: + grad = np.load(wd + "score_compression/grads_class.npy") + logger.info("Computing the gradient of CLASS wrt the cosmological parameters done.") + + grad_class = grad.T + + if not exists(wd + "score_compression/planck_Pk.npy") or force_score_compression: + from pysbmy.power import get_Pk + + logger.info("Computing the Planck spectrum...") + planck_Pk = get_Pk(k_s, params_planck_EH) + np.save(wd + "score_compression/planck_Pk", planck_Pk) + logger.info("Computing the Planck spectrum done.") + else: + planck_Pk = np.load(wd + "score_compression/planck_Pk.npy") + + logger.diagnostic("Plotting the gradients of CLASS...") + plt.figure(figsize=(14, 10)) + names_of_parameters = [ + r"$h$", + r"$\Omega_b$", + r"$\Omega_m$", + r"$n_s$", + r"$\sigma_8$", + ] + fig, ax = plt.subplots(3, 2, figsize=(14, 15)) + u, v = (-1, -1) + ax[u, v].loglog(k_s, planck_Pk) + ax[u, v].set_xlabel(r"$k$ [h/Mpc]") + ax[u, v].set_ylabel(r"$P(k)$") + ax[u, v].set_title(r"$P=\mathcal{T}(\omega_{\rm Planck})$") + for k in k_s: + ax[u, v].axvline(k, color="k", alpha=0.1, linewidth=0.5) + for i in range(len(planck_mean)): + u = i // 2 + v = i % 2 + for k in k_s: + ax[u, v].axvline(k, color="k", alpha=0.1, linewidth=0.5) + ax[u, v].plot(k_s, grad[i]) + ax[u, v].set_xscale("log") + ax[u, v].set_xlabel(r"$k$ [h/Mpc]") + ax[u, v].set_ylabel(r"$\partial P(k)/\partial$" + names_of_parameters[i]) + ax[u, v].set_title("Gradient wrt " + names_of_parameters[i]) + plt.suptitle("Gradient of Simbelmynë wrt cosmological parameters", fontsize=20) + plt.tight_layout() + plt.savefig( + figuresdir + "gradient_class.png", + bbox_inches="tight", + dpi=300, + transparent=True, + ) + plt.savefig(figuresdir + "gradient_class.pdf", bbox_inches="tight", dpi=300) + plt.close() + logger.diagnostic("Plotting the gradients of CLASS done.") + + if prefix_mocks is None: + logger.info("Computing Fisher matrix for the well specified model...") + else: + logger.info( + "Computing Fisher matrix for the misspecified model %s...", prefix_mocks + ) + params_ids_fisher = np.linspace(0, 4, 5, dtype=int) + + dw_f0 = selfi.likelihood.grad_f.dot(grad_class)[:, params_ids_fisher] + C0_inv = np.linalg.inv(selfi.likelihood.C_0) + F0 = dw_f0.T.dot(C0_inv).dot(dw_f0) + + if not exists(scoredir + "dw_f0.npy") or force_score_compression: + np.save(scoredir + "dw_f0.npy", dw_f0) + if not exists(scoredir + "C0_inv.npy") or force_score_compression: + np.save(scoredir + "C0_inv.npy", C0_inv) + if not exists(scoredir + "F0.npy") or force_score_compression: + np.save(scoredir + "F0.npy", F0) + + f0 = selfi.likelihood.f_0 + np.save(scoredir + "f0_expansion.npy", f0) + + plot_fisher( + F0, + names_of_parameters, + title="Fisher matrix", + path=figuresdir + "fisher.png", + ) + logger.info("Fisher matrix for the well specified model done.") + UNINDENT() + logger.info("Score compression done.") + + logger.info("Computing additional statistics...") + INDENT() + + logger.info("Computing the Mahalanobis distances...") + INDENT() + prior_theta_icov = selfi.prior.inv_covariance + diff = posterior_theta_mean - prior_theta_mean + Mahalanobis_distance = np.sqrt(diff.dot(prior_theta_icov).dot(diff)) + logger.info( + "Mahalanobis distance between the posterior and the prior: %s", Mahalanobis_distance + ) + np.savetxt(resultsdir_obs + "Mahalanobis_distances.txt", [Mahalanobis_distance]) + + diff = theta_gt / P_0 - posterior_theta_mean + Mahalanobis_distance_gt = np.sqrt(diff.dot(posterior_theta_icov).dot(diff)) + logger.info( + "Mahalanobis distance between the groundtruth and the posterior: %s", + Mahalanobis_distance_gt, + ) + np.savetxt(resultsdir_obs + "Mahalanobis_distances_gt.txt", [Mahalanobis_distance_gt]) + + NMahal = 2000 + logger.info( + "Computing the mean Mahalanobis distance between the prior and %s samples...", NMahal + ) + Mahalanobis_distances = [] + for _ in range(NMahal): + theta = np.random.multivariate_normal(prior_theta_mean, prior_theta_covariance) + diff = theta - prior_theta_mean + mahal = np.sqrt(diff.dot(prior_theta_icov).dot(diff)) + Mahalanobis_distances.append(mahal) + mean_Mahalanobis_distance = np.mean(Mahalanobis_distances) + logger.info( + "Mean Mahalanobis distance between the prior and %s samples: %s", + NMahal, + mean_Mahalanobis_distance, + ) + np.savetxt(resultsdir_obs + "mean_Mahalanobis_distances.txt", [mean_Mahalanobis_distance]) + UNINDENT() + logger.info("Computing the Mahalanobis distances done.") + + logger.diagnostic("Plotting the Mahalanobis distances...") + plot_histogram( + Mahalanobis_distances, + Mahalanobis_distance, + suptitle="Mahalanobis distances between the prior and samples", + savepath=figuresdir + "Mahalanobis_distances.png", + ) + logger.diagnostic("Plotting the Mahalanobis distances done.") + UNINDENT() + + except OSError as e: + logger.error("Directory or file access error: %s", str(e)) + raise + except Exception as e: + logger.critical("An unexpected error occurred: %s", str(e)) + raise RuntimeError("Pipeline step 3 failed.") from e + finally: + gc.collect() + logger.info("step 3 of the SelfiSys pipeline: done.") diff --git a/src/selfisys/preamble.tex b/src/selfisys/preamble.tex new file mode 100644 index 0000000..4202bf6 --- /dev/null +++ b/src/selfisys/preamble.tex @@ -0,0 +1,14 @@ +% ---------------------------------------------------------------------- +% Copyright (C) 2024 Tristan Hoellinger +% Distributed under the GNU General Public License v3.0 (GPLv3). +% See the LICENSE file in the root directory for details. +% SPDX-License-Identifier: GPL-3.0-or-later +% ---------------------------------------------------------------------- + +% Author: Tristan Hoellinger +% Version: 0.1.0 +% Date: 2024 +% License: GPLv3 + +\usepackage{amsmath,amsfonts,amssymb,amsthm} +\usepackage{upgreek} diff --git a/src/selfisys/prior.py b/src/selfisys/prior.py new file mode 100644 index 0000000..61ee2e4 --- /dev/null +++ b/src/selfisys/prior.py @@ -0,0 +1,690 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Priors for the SelfiSys pipeline. This module provides: +- a Planck2018-based prior class (`planck_prior`) compatible with +pySelfi, adapted for the logic of the SelfiSys pipeline; +- wrappers for the selfi2019 prior from [leclercq2019primordial]. + + +Raises +------ +OSError + If file or directory paths are inaccessible. +RuntimeError + If unexpected HPC or multi-processing errors arise. +""" + +import gc + +from selfisys.utils.logger import getCustomLogger + +logger = getCustomLogger(__name__) + + +def get_summary(x, bins, normalisation=None, kmax=1.4): + """ + Compute a power-spectrum summary for given cosmological parameters. + + Parameters + ---------- + x : array-like + Cosmological parameters [h, Omega_b, Omega_m, n_s, sigma_8]. + bins : array-like + Wavenumber bins. + normalisation : float or None, optional + Normalisation constant to scale the resulting spectrum. + kmax : float, optional + Maximum wavenumber for get_Pk. + + Returns + ------- + theta : ndarray + The computed power-spectrum values, optionally normalised. + + Raises + ------ + RuntimeError + If the power-spectrum computation fails unexpectedly. + """ + from numpy import array + from pysbmy.power import get_Pk + from selfisys.utils.tools import cosmo_vector_to_Simbelmyne_dict + + try: + theta = get_Pk(bins, cosmo_vector_to_Simbelmyne_dict(x, kmax=kmax)) + if normalisation is not None: + theta /= normalisation + return array(theta) + except Exception as e: + logger.critical("Unexpected error in get_summary: %s", str(e)) + raise RuntimeError("Failed to compute power spectrum summary.") from e + finally: + gc.collect() + + +def worker_class(params): + """ + Worker function to compute power spectra with CLASS, compatible with + Python multiprocessing. + + Parameters + ---------- + params : tuple + (x, bins, normalisation, kmax) where x is an array-like of + cosmological parameters, bins is the wavenumber array, + normalisation is a float or None, and kmax is a float. + + Returns + ------- + theta : ndarray + Power-spectrum summary from `get_summary`. + """ + x, bins, normalisation, kmax = params + return get_summary(x, bins, normalisation, kmax) + + +class planck_prior: + """ + Custom prior for the SelfiSys pipeline. This is the prior used in + [hoellinger2024diagnosing], based on the Planck 2018 cosmological + parameters. + + This class provides methods to compute a power-spectrum prior from a + prior distribution of cosmological parameters, using a Gaussian fit. + See equation (7) in [hoellinger2024diagnosing]. + + Parameters + ---------- + Omega_mean : array-like + Mean of the prior distribution on cosmological parameters. + Omega_cov : array-like + Covariance matrix of the prior distribution on cosmological + parameters. + bins : array-like + Wavenumbers where the power spectrum is evaluated. + normalisation : float or None + If not None, divide the power spectra by the normalisation. + kmax : float + Maximum wavenumber for computations. + nsamples : int, optional + Number of samples drawn from the prior on the cosmological + parameters. Default is 10,000. + nthreads : int, optional + Number of CPU threads for parallel tasks. Default is -1, that + is, auto-detect the number of available threads. + EPS_K : float, optional + Regularisation parameter for covariance inversion. Default 1e-7. + EPS_residual : float, optional + Additional cutoff for matrix inversion. Default 1e-3. + filename : str or None, optional + Path to a .npy file to store or load precomputed power spectra. + + Attributes + ---------- + mean : ndarray + Mean of the computed power spectra. + covariance : ndarray + Covariance matrix of the computed power spectra. + inv_covariance : ndarray + Inverse of the covariance matrix. + + Raises + ------ + OSError + If file reading or writing fails. + RuntimeError + For unexpected HPC or multi-processing errors. + """ + + def __init__( + self, + Omega_mean, + Omega_cov, + bins, + normalisation, + kmax, + nsamples=10000, + nthreads=-1, + EPS_K=1e-7, + EPS_residual=1e-3, + filename=None, + ): + from numpy import where + from multiprocessing import cpu_count + + self.Omega_mean = Omega_mean + self.Omega_cov = Omega_cov + self.bins = bins + self.normalisation = normalisation + self.kmax = kmax + self.nsamples = nsamples + self.EPS_K = EPS_K + self.EPS_residual = EPS_residual + self.filename = filename + + if nthreads == -1: + # Use #CPU - 1 or fallback to 1 if a single CPU is available + self.nthreads = cpu_count() - 1 or 1 + else: + self.nthreads = nthreads + + self._Nbin_min = where(self.bins >= 0.01)[0].min() + self._Nbin_max = where(self.bins <= self.kmax)[0].max() + 1 + + # Attributes set after compute() + self.mean = None + self.covariance = None + self.inv_covariance = None + self.thetas = None + + @property + def Nbin_min(self, k_min): + """Index of the minimal wavenumber given k_min.""" + return self._Nbin_min + + @property + def Nbin_max(self, k_min): + """Index of the maximal wavenumber given self.kmax.""" + return self._Nbin_max + + def compute(self): + """ + Compute the prior (mean, covariance, and inverse covariance). + + If `self.filename` exists, tries to load the prior. Otherwise, + samples from the prior distribution on cosmological parameters + and evaluates the power spectra in parallel. + + Raises + ------ + OSError + If self.filename is not writable/accessible. + RuntimeError + If multi-processing or power-spectra computations fail. + """ + from os.path import exists + import numpy as np + + try: + if self.filename and exists(self.filename): + logger.info("Loading precomputed thetas from %s", self.filename) + self.thetas = np.load(self.filename) + else: + from time import time + from multiprocessing import Pool + import tqdm.auto as tqdm + + logger.info("Sampling %d cosmological parameter sets...", self.nsamples) + OO = np.random.multivariate_normal( + np.array(self.Omega_mean), np.array(self.Omega_cov), self.nsamples + ) + eps = 1e-5 + OO = np.clip(OO, eps, 1 - eps) + + liste = [(o, self.bins, self.normalisation, self.kmax) for o in OO] + + logger.info( + "Computing prior power spectra in parallel using %d threads...", self.nthreads + ) + start = time() + with Pool(self.nthreads) as pool: + thetas = [] + for theta in tqdm.tqdm(pool.imap(worker_class, liste), total=len(liste)): + thetas.append(theta) + thetas = np.array(thetas) + end = time() + logger.info("Done computing power spectra in %.2f seconds.", end - start) + + self.thetas = thetas + if self.filename: + logger.info("Saving thetas to %s", self.filename) + np.save(self.filename, thetas) + + # Compute stats + self.mean = np.mean(self.thetas, axis=0) + self.covariance = np.cov(self.thetas.T) + + logger.info("Regularising and inverting the prior covariance matrix.") + from pyselfi.utils import regular_inv + + self.inv_covariance = regular_inv(self.covariance, self.EPS_K, self.EPS_residual) + + except OSError as e: + logger.error("File I/O error: %s", str(e)) + raise + except Exception as e: + logger.critical("Error during prior computation: %s", str(e)) + raise RuntimeError("planck_prior computation failed.") from e + finally: + gc.collect() + + def logpdf(self, theta, theta_mean, theta_covariance, theta_icov): + """ + Return the log prior probability at a given point in parameter + space. + + Parameters + ---------- + theta : ndarray + Evaluation point in parameter space. + theta_mean : ndarray + Prior mean vector. + theta_covariance : ndarray + Prior covariance matrix. + theta_icov : ndarray + Inverse of the prior covariance matrix. + + Returns + ------- + float + Log prior probability value. + """ + import numpy as np + + diff = theta - theta_mean + val = -0.5 * diff.dot(theta_icov).dot(diff) + val -= 0.5 * np.linalg.slogdet(2 * np.pi * theta_covariance)[1] + return val + + def sample(self, seedsample=None): + """ + Draw a random sample from the prior distribution. + + Parameters + ---------- + seedsample : int, optional + Seed for the random number generator. + + Returns + ------- + ndarray + A single sample from the prior distribution. + """ + from numpy.random import seed, multivariate_normal + + if seedsample is not None: + seed(seedsample) + return multivariate_normal(self.mean, self.covariance) + + def save(self, fname): + """ + Save the prior to an output file. + + Parameters + ---------- + fname : str + Output HDF5 filename to store the prior data. + + Raises + ------ + OSError + If the file cannot be accessed or written. + """ + import h5py + from ctypes import c_double + from pyselfi.utils import PrintMessage, save_replace_dataset, save_replace_attr + + try: + PrintMessage(3, f"Writing prior in data file '{fname}'...") + with h5py.File(fname, "r+") as hf: + + def save_to_hf(name, data, **kwargs): + save_replace_dataset(hf, f"/prior/{name}", data, dtype=c_double, **kwargs) + + # Hyperparameters + save_to_hf("thetas", self.thetas, maxshape=(None, None)) + save_to_hf("Omega_mean", self.Omega_mean, maxshape=(None,)) + save_to_hf("Omega_cov", self.Omega_cov, maxshape=(None, None)) + save_to_hf("bins", self.bins, maxshape=(None,)) + + save_replace_attr(hf, "/prior/normalisation", self.normalisation, dtype=c_double) + save_replace_attr(hf, "/prior/kmax", self.kmax, dtype=c_double) + + # Mandatory attributes + save_to_hf("mean", self.mean, maxshape=(None,)) + save_to_hf("covariance", self.covariance, maxshape=(None, None)) + save_to_hf("inv_covariance", self.inv_covariance, maxshape=(None, None)) + + PrintMessage(3, f"Writing prior in data file '{fname}' done.") + except OSError as e: + logger.error("Failed to save prior to '%s': %s", fname, str(e)) + raise + finally: + gc.collect() + + @classmethod + def load(cls, fname): + """ + Load the prior from input file. + + Parameters + ---------- + fname : str + Input HDF5 filename. + + Returns + ------- + prior + The prior object. + + Raises + ------ + OSError + If the file cannot be read or is invalid. + """ + from h5py import File + from numpy import array + from ctypes import c_double + from pyselfi.utils import PrintMessage + + try: + PrintMessage(3, f"Reading prior in data file '{fname}'...") + with File(fname, "r") as hf: + # Load constructor parameters + Omega_mean = array(hf.get("/prior/Omega_mean"), dtype=c_double) + Omega_cov = array(hf.get("/prior/Omega_cov"), dtype=c_double) + bins = array(hf.get("/prior/bins"), dtype=c_double) + normalisation = hf.attrs["/prior/normalisation"] + kmax = hf.attrs["/prior/kmax"] + + # Instantiate class + prior = cls(Omega_mean, Omega_cov, bins, normalisation, kmax) + + # Load mandatory arrays + prior.mean = array(hf.get("prior/mean"), dtype=c_double) + prior.covariance = array(hf.get("/prior/covariance"), dtype=c_double) + prior.inv_covariance = array(hf.get("/prior/inv_covariance"), dtype=c_double) + + PrintMessage(3, f"Reading prior in data file '{fname}' done.") + return prior + except OSError as e: + logger.error("Failed to read prior from '%s': %s", fname, str(e)) + raise + finally: + gc.collect() + + +def logposterior_hyperparameters_parallel( + selfi, + theta_fiducial, + Nbin_min, + Nbin_max, + theta_norm, + k_corr, + alpha_cv, +): + """ + Compute the log-posterior for the hyperparameters of the prior from + [leclercq2019primordial], for use within the SelfiSys pipeline. + + Parameters + ---------- + selfi : object + The selfi object. + theta_fiducial : ndarray + Fiducial spectrum. + Nbin_min : int + Minimum bin index for the wavenumber range. + Nbin_max : int + Maximum bin index for the wavenumber range. + theta_norm : float + Hyperparameter controlling the overall uncertainty. + k_corr : float + Hyperparameter controlling correlation scale. + alpha_cv : float + Cosmic variance strength. + + Returns + ------- + float + The log-posterior value for the given hyperparameters. + + Raises + ------ + RuntimeError + If the log-posterior computation fails unexpectedly. + """ + try: + return selfi.logposterior_hyperparameters( + theta_fiducial, Nbin_min, Nbin_max, theta_norm, k_corr, alpha_cv + ) + except Exception as e: + logger.critical("Unexpected error in logposterior_hyperparameters_parallel: %s", str(e)) + raise RuntimeError("logposterior_hyperparameters_parallel failed.") from e + finally: + gc.collect() + + +def perform_prior_optimisation_and_plot( + selfi, + theta_fiducial, + theta_norm_mean=0.1, + theta_norm_std=0.3, + k_corr_mean=0.020, + k_corr_std=0.015, + k_opt_min=0.0, + k_opt_max=1.4, + theta_norm_min=0.04, + theta_norm_max=0.12, + k_corr_min=0.012, + k_corr_max=0.02, + meshsize=30, + Nbin_min=0, + Nbin_max=100, + theta_norm=0.05, + k_corr=0.015, + alpha_cv=0.00065, + plot=True, + savepath=None, +): + """ + Optimise the hyperparameters for the selfi2019 prior (from + [leclercq2019primordial]). + + Parameters + ---------- + selfi : object + The selfi object. + theta_fiducial : ndarray + Fiducial spectrum. + theta_norm_mean : float, optional + Mean of the Gaussian hyperprior on theta_norm. Default 0.1. + theta_norm_std : float, optional + Standard deviation of the hyperprior on theta_norm. Default 0.3. + k_corr_mean : float, optional + Mean of the Gaussian hyperprior on k_corr. Default 0.020. + k_corr_std : float, optional + Standard deviation of the hyperprior on k_corr. Default 0.015. + k_opt_min : float, optional + Minimum wavenumber for the prior optimisation. Default 0.0. + k_opt_max : float, optional + Maximum wavenumber for the prior optimisation. Default 1.4. + theta_norm_min : float, optional + Lower bound for theta_norm in the mesh. Default 0.04. + theta_norm_max : float, optional + Upper bound for theta_norm in the mesh. Default 0.12. + k_corr_min : float, optional + Lower bound for k_corr in the mesh. Default 0.012. + k_corr_max : float, optional + Upper bound for k_corr in the mesh. Default 0.02. + meshsize : int, optional + Number of points in each dimension of the plot mesh. Default 30. + Nbin_min : int, optional + Minimum bin index for restricting the prior. Default 0. + Nbin_max : int, optional + Maximum bin index for restricting the prior. Default 100. + theta_norm : float, optional + Initial or default guess of theta_norm. Default 0.05. + k_corr : float, optional + Initial or default guess of k_corr. Default 0.015. + alpha_cv : float, optional + Cosmic variance term or similar. Default 0.00065. + plot : bool, optional + If True, generate and show/save a 2D contour plot. Default True. + savepath : str, optional + File path to save the plot. If None, the plot is displayed. + + Returns + ------- + tuple + (theta_norm, k_corr) after optimisation. + + Raises + ------ + OSError + If file operations fail during saving the prior or posterior. + RuntimeError + If the optimisation fails unexpectedly. + """ + try: + if plot: + from selfisys.utils.plot_utils import get_contours + from numpy import meshgrid, linspace, zeros, exp, array + from joblib import Parallel, delayed + + logger.info("Preparing the hyperparameter grid for plotting (meshsize=%d).", meshsize) + + X0, Y0 = meshgrid( + linspace(theta_norm_min, theta_norm_max, meshsize), + linspace(k_corr_min, k_corr_max, meshsize), + ) + Z = zeros((meshsize, meshsize)) + + # Evaluate log-posterior on the grid in parallel + Z = array( + Parallel(n_jobs=-1)( + delayed(logposterior_hyperparameters_parallel)( + selfi, + theta_fiducial, + Nbin_min, + Nbin_max, + X0[i][j], + Y0[i][j], + alpha_cv, + ) + for i in range(meshsize) + for j in range(meshsize) + ) + ).reshape(meshsize, meshsize) + Z -= Z.max() + Z = exp(Z) + + Z_contours = get_contours(Z, meshsize) + logger.info("Grid evaluations complete.") + + logger.info("Performing the prior hyperparameter optimisation...") + selfi.prior.theta_norm = theta_norm + selfi.prior.k_corr = k_corr + selfi.prior.alpha_cv = alpha_cv + + # Perform the prior optimisation + x0 = [theta_norm, k_corr] + selfi.optimize_prior( + theta_fiducial, + k_opt_min, + k_opt_max, + x0=x0, + theta_norm_min=theta_norm_min, + theta_norm_max=theta_norm_max, + theta_norm_mean=theta_norm_mean, + theta_norm_std=theta_norm_std, + k_corr_min=k_corr_min, + k_corr_max=k_corr_max, + k_corr_mean=k_corr_mean, + k_corr_std=k_corr_std, + options={ + "maxiter": 30, + "ftol": 1e-10, + "gtol": 1e-10, + "eps": 1e-6, + "disp": False, + }, + ) + + logger.info("Saving prior and posterior after optimisation.") + selfi.save_prior() + selfi.save_posterior() + + theta_norm = selfi.prior.theta_norm + k_corr = selfi.prior.k_corr + + prior_theta_mean, prior_theta_covariance = selfi.prior.mean, selfi.prior.covariance + prior_theta_mean = prior_theta_mean[Nbin_min:Nbin_max] + prior_theta_covariance = prior_theta_covariance[Nbin_min:Nbin_max, Nbin_min:Nbin_max] + + posterior_theta_mean, posterior_theta_covariance, posterior_theta_icov = ( + selfi.restrict_posterior(Nbin_min, Nbin_max) + ) + + logger.info("Optimised hyperparameters: theta_norm=%.5f, k_corr=%.5f", theta_norm, k_corr) + + if plot: + import matplotlib.pyplot as plt + + fig, ax = plt.subplots(figsize=(6, 5)) + ax.xaxis.set_ticks_position("both") + ax.yaxis.set_ticks_position("both") + ax.xaxis.set_tick_params(which="both", direction="in", width=1.0) + ax.xaxis.set_tick_params(which="major", length=6, labelsize=17) + ax.xaxis.set_tick_params(which="minor", length=4) + ax.yaxis.set_tick_params(which="both", direction="in", width=1.0) + ax.yaxis.set_tick_params(which="major", length=6, labelsize=17) + + pcm = ax.pcolormesh(X0, Y0, Z, cmap="Greys", shading="gouraud") + ax.grid(linestyle=":") + ax.contour( + Z, + Z_contours, + extent=[theta_norm_min, theta_norm_max, k_corr_min, k_corr_max], + colors="C9", + ) + ax.plot( + [x0[0], x0[0]], + [k_corr_min, k_corr_max], + color="C3", + linestyle=":", + label="Before optimisation", + ) + ax.plot([theta_norm_min, theta_norm_max], [x0[1], x0[1]], color="C3", linestyle=":") + ax.plot( + [theta_norm, theta_norm], + [k_corr_min, k_corr_max], + linestyle="--", + color="C3", + label="After optimisation", + ) + ax.plot([theta_norm_min, theta_norm_max], [k_corr, k_corr], linestyle="--", color="C3") + + ax.set_xlabel(r"$\theta_\mathrm{norm}$", size=19) + ax.set_ylabel(r"$k_\mathrm{corr}$ [$h$/Mpc]", size=19) + ax.legend() + + if savepath is None: + plt.show() + else: + fig.savefig(savepath, bbox_inches="tight", dpi=300, format="png", transparent=True) + fig.savefig(savepath[:-4] + ".pdf", bbox_inches="tight", dpi=300, format="pdf") + plt.close(fig) + + return theta_norm, k_corr + + except OSError as e: + logger.error("File access or I/O error: %s", str(e)) + raise + except Exception as e: + logger.critical("Unexpected error in perform_prior_optimisation_and_plot: %s", str(e)) + raise RuntimeError("perform_prior_optimisation_and_plot failed.") from e + finally: + gc.collect() diff --git a/src/selfisys/sbmy_interface.py b/src/selfisys/sbmy_interface.py new file mode 100644 index 0000000..9546cb4 --- /dev/null +++ b/src/selfisys/sbmy_interface.py @@ -0,0 +1,889 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +"""Simbelmynë-related functions for the SelfiSys pipeline. +""" + +import os +import gc +from typing import Optional, List, Tuple + +from selfisys.utils.logger import getCustomLogger, INDENT, UNINDENT + +logger = getCustomLogger(__name__) + + +def get_power_spectrum_from_cosmo( + L, + size, + cosmo, + fname_power_spectrum, + force=False, +): + """ + Compute a power spectrum from cosmological parameters and save it to + disk. + + Parameters + ---------- + L : float + Size of the simulation box (in Mpc/h). + size : int + Number of grid points along each axis. + cosmo : dict + Cosmological parameters (and infrastructure parameters). + fname_power_spectrum : str + Name (including path) of the power spectrum file to read/write. + force : bool, optional + If True, forces recomputation even if the file exists. Default + is False. + + Raises + ------ + OSError + If file writing fails or the directory path is invalid. + RuntimeError + For unexpected issues during power spectrum computation. + """ + if not os.path.exists(fname_power_spectrum) or force: + from pysbmy.power import PowerSpectrum + + try: + logger.debug("Computing power spectrum for L=%.2f, size=%d", L, size) + P = PowerSpectrum(L, L, L, size, size, size, cosmo) + P.write(fname_power_spectrum) + logger.debug("Power spectrum written to %s", fname_power_spectrum) + except OSError as e: + logger.error("File write error at %s: %s", fname_power_spectrum, str(e)) + raise + except Exception as e: + logger.critical("Unexpected error in power spectrum computation: %s", str(e)) + raise RuntimeError("get_power_spectrum_from_cosmo failed.") from e + finally: + gc.collect() + + +def compute_Phi( + G_ss_path, + P_ss_path, + g_obj, + norm, + AliasingCorr=True, + verbosity=1, +): + """ + Compute the summary statistics from a field object, based on a + provided summary-statistics Fourier grid and baseline spectrum. + + Parameters + ---------- + G_ss_path : str + Path to the FourierGrid file used for summary-statistics. + P_ss_path : str + Path to the baseline power spectrum file for normalisation. + g_obj : Field + Input field object from which to compute summary statistics. + norm : ndarray + Normalisation constants for the summary statistics. + AliasingCorr : bool, optional + Whether to apply aliasing correction. Default is True. + verbosity : int, optional + Verbosity level (0=quiet, 1=normal, 2=debug). Default 1. + + Returns + ------- + Phi : ndarray + Vector of summary statistics. + + Raises + ------ + OSError + If file reading fails at G_ss_path or P_ss_path. + RuntimeError + If unexpected issues occur during computation. + """ + from pysbmy.correlations import get_autocorrelation + from pysbmy.power import FourierGrid, PowerSpectrum + from pysbmy import c_double + from io import BytesIO + + try: + logger.debug("Reading FourierGrid from %s", G_ss_path) + G_ss = FourierGrid.read(G_ss_path) + + if verbosity > 1: + Pk, _ = get_autocorrelation(g_obj, G_ss, AliasingCorr=AliasingCorr) + else: + from selfisys.utils.low_level import stdout_redirector + + f = BytesIO() + with stdout_redirector(f): + Pk, _ = get_autocorrelation(g_obj, G_ss, AliasingCorr=AliasingCorr) + f.close() + + logger.debug("Reading baseline PowerSpectrum from %s", P_ss_path) + P_ss = PowerSpectrum.read(P_ss_path) + Phi = Pk / (norm * P_ss.powerspectrum) + + del G_ss, P_ss + gc.collect() + return Phi.astype(c_double) + except OSError as e: + logger.error("File not found or inaccessible: %s", str(e)) + raise + except Exception as e: + logger.critical("Unexpected error in compute_Phi: %s", str(e)) + raise RuntimeError("compute_Phi failed.") from e + finally: + gc.collect() + + +def generate_white_noise_Field( + L, + size, + seedphase, + fname_whitenoise, + seedname_whitenoise, + force_phase=False, +): + """ + Generate a white noise realisation in physical space and write it to + disk. + + Parameters + ---------- + L : float + Size of the simulation box (in Mpc/h). + size : int + Number of grid points along each axis. + seedphase : int or list of int + User-provided seed to generate the initial white noise. + fname_whitenoise : str + File path to write the white noise realisation. + seedname_whitenoise : str + File path to write the seed state of the RNG. + force_phase : bool, optional + If True, forces regeneration of the random phases. Default is + False. + + Raises + ------ + OSError + If file writing fails or directory paths are invalid. + RuntimeError + For unexpected issues. + """ + if not os.path.exists(fname_whitenoise) or force_phase: + import numpy as np + from pysbmy.field import BaseField + + try: + logger.debug("Generating white noise for L=%.2f, size=%d", L, size) + rng = np.random.default_rng(seedphase) + + logger.debug("Saving RNG state to %s", seedname_whitenoise) + np.save(seedname_whitenoise, rng.bit_generator.state) + with open(seedname_whitenoise + ".txt", "w") as f: + f.write(str(rng.bit_generator.state)) + + data = rng.standard_normal(size=size**3) + wn = BaseField(L, L, L, 0, 0, 0, 1, size, size, size, data) + del data + + wn.write(fname_whitenoise) + logger.debug("White noise field written to %s", fname_whitenoise) + del wn + except OSError as e: + logger.error("Writing white noise failed at '%s': %s", fname_whitenoise, str(e)) + raise + except Exception as e: + logger.critical("Unexpected error in generate_white_noise_Field: %s", str(e)) + raise RuntimeError("generate_white_noise_Field failed.") from e + finally: + gc.collect() + + +def setup_sbmy_parfiles( + d, + cosmology, + file_names, + hiddenbox_params, + force=False, +): + """Set up Simbelmynë parameter file (please refer to the Simbelmynë + documentation for more details). + + Parameters + ---------- + d : int + Index (from 1 to S) specifying a direction in parameter space, 0 + for the expansion point, or -1 for mock data. + cosmology : array, double, dimension=5 + Cosmological parameters. + file_names : dict + Dictionary containing the names of the input/output files for + the simulation. + hiddenbox_params : dict + See the `HiddenBox` class for more details. + force : bool, optional, default=False + If True, forces recompute the simulation parameter files. + + """ + from os.path import exists + + fname_simparfile = file_names["fname_simparfile"] + fname_power_spectrum = file_names["fname_power_spectrum"] + fname_whitenoise = file_names["fname_whitenoise"] + fname_outputinitialdensity = file_names["fname_outputinitialdensity"] + fnames_outputrealspacedensity = file_names["fnames_outputrealspacedensity"] + fnames_outputdensity = file_names["fnames_outputdensity"] + fnames_outputLPTdensity = file_names["fnames_outputLPTdensity"] + + Npop = hiddenbox_params["Npop"] + Np0 = hiddenbox_params["Np0"] + Npm0 = hiddenbox_params["Npm0"] + size = hiddenbox_params["size"] + L = hiddenbox_params["L"] + Ntimesteps = hiddenbox_params["Ntimesteps"] + sim_params = hiddenbox_params["sim_params"] + eff_redshifts = hiddenbox_params["eff_redshifts"] + TimeSteps = hiddenbox_params["TimeSteps"] + TimeStepDistribution = hiddenbox_params["TimeStepDistribution"] + modified_selfi = hiddenbox_params["modified_selfi"] + fsimdir = hiddenbox_params["fsimdir"] + + if not exists(fname_simparfile + "_{}.sbmy".format(Npop)) or force: + from pysbmy import param_file + from re import search + from selfisys.global_parameters import BASEID_OBS + + if TimeSteps is not None and eff_redshifts is None: + raise ValueError("TimeSteps must be provided if eff_redshifts is None.") + + regex = r"([a-zA-Z]+)(\d+)?([a-zA-Z]+)?(\d+)?([a-zA-Z]+)?" + m = search(regex, sim_params) + if m.group(1) == "std": + # Single LPT+COLA/PM Simbelmynë data card with linear time + # stepping + if m.group(2)[0] == "0": + RedshiftLPT = float("0." + m.group(2)[1:]) + else: + RedshiftLPT = int(m.group(2)) + + RedshiftFCs = 0.0 + WriteLPTSnapshot = 0 + WriteLPTDensity = 0 + match m.group(3): + case "RSD": + ModulePMCOLA = 0 + EvolutionMode = 2 + NumberOfTimeSteps = 0 + RedshiftFCs = RedshiftLPT + NonLinearRSD = 1 + case "PM": + ModulePMCOLA = 1 + EvolutionMode = 1 + NumberOfTimeSteps = m.group(4) + NonLinearRSD = 0 if (m.group(5) and m.group(5)[:3] == "lin") else 1 + case "COLA": + ModulePMCOLA = 1 + EvolutionMode = 2 + NumberOfTimeSteps = m.group(4) + NonLinearRSD = 0 if (m.group(5) and m.group(5)[:3] == "lin") else 1 + case _: + raise ValueError("sim_params = {} not valid".format(sim_params)) + NumberOfTimeSteps = int(m.group(4)) if m.group(4) is not None else 0 + elif m.group(1) == "custom": + # Single LPT+COLA/PM Simbelmynë card with user-provided time + # stepping object + RedshiftLPT = int(m.group(2)) + match m.group(3): + case None: + ModulePMCOLA = 0 + EvolutionMode = 2 + case "PM": + ModulePMCOLA = 1 + EvolutionMode = 1 + NonLinearRSD = 0 if (m.group(5) and m.group(5)[:3] == "lin") else 1 + case "COLA": + ModulePMCOLA = 1 + EvolutionMode = 2 + NonLinearRSD = 0 if (m.group(5) and m.group(5)[:3] == "lin") else 1 + case _: + raise ValueError("sim_params = {} not valid".format(sim_params)) + if TimeStepDistribution is None: + raise ValueError("TimeStepDistribution must be provided for 'custom'.") + elif m.group(1) == "splitLPT": + # Use as many Simbelmynë data cards as there are populations + # of galaxies + if eff_redshifts is None: + raise ValueError("eff_redshifts must be provided for 'splitLPT'.") + elif len(eff_redshifts) != Ntimesteps: + raise ValueError("len(eff_redshifts) != Ntimesteps") + elif m.group(1) == "split": + # Use as many Simbelmynë data cards as there are populations + # of galaxies + if TimeStepDistribution is None: + raise ValueError("TimeStepDistribution must be for 'split'.") + if eff_redshifts is None: + raise ValueError("eff_redshifts must be provided for 'split'.") + elif len(eff_redshifts) != Ntimesteps: + raise ValueError("len(eff_redshifts) != Ntimesteps") + + RedshiftLPT = int(m.group(2)) + match m.group(3): + case "RSD": + ModulePMCOLA = 1 + EvolutionMode = 2 + NonLinearRSD = 1 + case _: + raise ValueError("sim_params = {} not valid".format(sim_params)) + NumberOfTimeSteps = int(m.group(4)) if m.group(4) is not None else 0 + else: + raise ValueError("sim_params = {} not valid" + sim_params) + + if sim_params[-3:] == BASEID_OBS: + from selfisys.global_parameters import ( + h_obs as h, + Omega_b_obs as Omega_b, + Omega_m_obs as Omega_m, + nS_obs as nS, + sigma8_obs as sigma8, + ) + else: + if modified_selfi: + # Treat the cosmological parameters as nuisance + # parameters within the hidden box forward model + h, Omega_b, Omega_m, nS, sigma8 = cosmology + else: + # Fix the fiducial cosmology within the hidden box + from selfisys.global_parameters import ( + h_planck as h, + Omega_b_planck as Omega_b, + Omega_m_planck as Omega_m, + nS_planck as nS, + sigma8_planck as sigma8, + ) + + if d < 0: # -1 for mock data, -2 to recompute the observations + WriteInitialConditions = 1 + WriteDensities = 1 # also write real space density fields + else: # d=0 for expansion point or d>0 for the gradients + WriteInitialConditions = 0 + WriteDensities = 1 # also write real space density fields + + if m.group(1) == "std": + S = param_file( ## Module LPT ## + ModuleLPT=1, + # Basic setup: + Particles=Np0, + Mesh=size, + BoxSize=L, + corner0=0.0, + corner1=0.0, + corner2=0.0, + # Initial conditions: + ICsMode=1, + WriteICsRngState=0, + WriteInitialConditions=WriteInitialConditions, + InputWhiteNoise=fname_whitenoise, + OutputInitialConditions=fname_outputinitialdensity, + # Power spectrum: + InputPowerSpectrum=fname_power_spectrum, + # Final conditions for LPT: + RedshiftLPT=RedshiftLPT, + WriteLPTSnapshot=WriteLPTSnapshot, + WriteLPTDensity=WriteLPTDensity, + OutputLPTDensity=fnames_outputLPTdensity, + #################### + ## Module PM/COLA ## + #################### + ModulePMCOLA=ModulePMCOLA, + EvolutionMode=EvolutionMode, # 1 for PM, 2 for COLA + ParticleMesh=Npm0, + NumberOfTimeSteps=NumberOfTimeSteps, + # Final snapshot: + RedshiftFCs=RedshiftFCs, + WriteFinalSnapshot=0, + WriteFinalDensity=WriteDensities, + OutputFinalDensity=fnames_outputrealspacedensity[0], + ######### + ## RSD ## + ######### + ModuleRSD=1, + WriteIntermediaryRSD=0, + DoNonLinearMapping=NonLinearRSD, + WriteRSDensity=1, + OutputRSDensity=fnames_outputdensity[0], + ############################# + ## Cosmological parameters ## + ############################# + h=h, + Omega_q=1.0 - Omega_m, + Omega_b=Omega_b, + Omega_m=Omega_m, + Omega_k=0.0, + n_s=nS, + sigma8=sigma8, + w0_fld=-1.0, + wa_fld=0.0, + ) + S.write(fname_simparfile + "_{}.sbmy".format(Npop)) + elif m.group(1) == "custom": + RedshiftFCs = eff_redshifts + fname_outputdensity = ( + fnames_outputdensity[0][: fnames_outputdensity[0].rfind("_")] + ".h5" + ) + S = param_file( ## Module LPT ## + ModuleLPT=1, + # Basic setup: + Particles=Np0, + Mesh=size, + BoxSize=L, + corner0=0.0, + corner1=0.0, + corner2=0.0, + # Initial conditions: + ICsMode=1, + WriteICsRngState=0, + WriteInitialConditions=WriteInitialConditions, + InputWhiteNoise=fname_whitenoise, + OutputInitialConditions=fname_outputinitialdensity, + # Power spectrum: + InputPowerSpectrum=fname_power_spectrum, + # Final conditions for LPT: + RedshiftLPT=RedshiftLPT, + WriteLPTSnapshot=0, + WriteLPTDensity=0, + #################### + ## Module PM/COLA ## + #################### + ModulePMCOLA=ModulePMCOLA, + EvolutionMode=EvolutionMode, # 1 for PM, 2 for COLA + ParticleMesh=Npm0, + OutputKickBase=fsimdir + "/data/cola_kick_", + # Final snapshot: + RedshiftFCs=RedshiftFCs, + WriteFinalSnapshot=0, + WriteFinalDensity=0, + OutputFinalDensity=fnames_outputrealspacedensity[0], + # Intermediate snapshots: + WriteSnapshots=0, + WriteDensities=WriteDensities, + OutputDensitiesBase=fnames_outputrealspacedensity[0][ + : fnames_outputrealspacedensity[0].rfind("_") + ] + + "_", + OutputDensitiesExt=".h5", + ############################ + ## Time step distribution ## + ############################ + TimeStepDistribution=TimeStepDistribution, + ModifiedDiscretization=1, # Modified KD discretisation + n_LPT=-2.5, # Exponent for the Ansatz in KD operators + ######### + ## RSD ## + ######### + ModuleRSD=1, + WriteIntermediaryRSD=1, + DoNonLinearMapping=NonLinearRSD, + WriteRSDensity=1, + OutputRSDensity=fname_outputdensity, + ############################# + ## Cosmological parameters ## + ############################# + h=h, + Omega_q=1.0 - Omega_m, + Omega_b=Omega_b, + Omega_m=Omega_m, + Omega_k=0.0, + n_s=nS, + sigma8=sigma8, + w0_fld=-1.0, + wa_fld=0.0, + ) + S.write(fname_simparfile + "_{}.sbmy".format(Npop)) + elif m.group(1) == "split": + datadir = fsimdir + "/data/" + RedshiftFCs = eff_redshifts[0] + + # Write the parameter file for the first simulation + S = param_file( + ################ + ## Module LPT ## + ################ + ModuleLPT=1, + # Basic setup: + Particles=Np0, + Mesh=size, + BoxSize=L, + corner0=0.0, + corner1=0.0, + corner2=0.0, + # Initial conditions: + ICsMode=1, + WriteICsRngState=0, + WriteInitialConditions=WriteInitialConditions, + InputWhiteNoise=fname_whitenoise, + OutputInitialConditions=fname_outputinitialdensity, + # Power spectrum: + InputPowerSpectrum=fname_power_spectrum, + # Final conditions for LPT: + RedshiftLPT=RedshiftLPT, + WriteLPTSnapshot=0, + WriteLPTDensity=0, + #################### + ## Module PM/COLA ## + #################### + ModulePMCOLA=ModulePMCOLA, + EvolutionMode=EvolutionMode, + ParticleMesh=Npm0, + OutputKickBase=datadir + "cola_kick_0_", + # Final snapshot: + RedshiftFCs=RedshiftFCs, + WriteFinalSnapshot=1, + OutputFinalSnapshot=datadir + "cola_snapshot_0.gadget3", + WriteFinalDensity=1, + OutputFinalDensity=fnames_outputrealspacedensity[0], + WriteLPTDisplacements=1, + OutputPsiLPT1=datadir + "lpt_psi1_0.h5", + OutputPsiLPT2=datadir + "lpt_psi2_0.h5", + ############################ + ## Time step distribution ## + ############################ + TimeStepDistribution=TimeStepDistribution[0], + ModifiedDiscretization=1, + ######### + ## RSD ## + ######### + ModuleRSD=1, + WriteIntermediaryRSD=0, + DoNonLinearMapping=NonLinearRSD, + WriteRSDensity=1, + OutputRSDensity=fnames_outputdensity[0], + ############################# + ## Cosmological parameters ## + ############################# + h=h, + Omega_q=1.0 - Omega_m, + Omega_b=Omega_b, + Omega_m=Omega_m, + Omega_k=0.0, + n_s=nS, + sigma8=sigma8, + w0_fld=-1.0, + wa_fld=0.0, + ) + S.write(fname_simparfile + "_pop0.sbmy") + + for i in range(1, Ntimesteps): + RedshiftFCs = eff_redshifts[i] + + S = param_file( + ModuleLPT=0, + # Basic setup: + Particles=Np0, + Mesh=size, + BoxSize=L, + corner0=0.0, + corner1=0.0, + corner2=0.0, + InputPsiLPT1=datadir + "lpt_psi1_0.h5", + InputPsiLPT2=datadir + "lpt_psi2_0.h5", + #################### + ## Module PM/COLA ## + #################### + ModulePMCOLA=ModulePMCOLA, + InputPMCOLASnapshot=datadir + "cola_snapshot_{:d}.gadget3".format(i - 1), + EvolutionMode=EvolutionMode, + ParticleMesh=Npm0, + OutputKickBase=datadir + "cola_kick_{:d}_".format(i), + # Final snapshot: + RedshiftFCs=RedshiftFCs, + WriteFinalSnapshot=1, + OutputFinalSnapshot=datadir + "cola_snapshot_{:d}.gadget3".format(i), + WriteFinalDensity=1, + OutputFinalDensity=fnames_outputrealspacedensity[::-1][i], + WriteLPTDisplacements=0, + ############################ + ## Time step distribution ## + ############################ + TimeStepDistribution=TimeStepDistribution[i], + ModifiedDiscretization=1, + ######### + ## RSD ## + ######### + ModuleRSD=1, + WriteIntermediaryRSD=0, + DoNonLinearMapping=NonLinearRSD, + WriteRSDensity=1, + OutputRSDensity=fnames_outputdensity[i], + ############################# + ## Cosmological parameters ## + ############################# + h=h, + Omega_q=1.0 - Omega_m, + Omega_b=Omega_b, + Omega_m=Omega_m, + Omega_k=0.0, + n_s=nS, + sigma8=sigma8, + w0_fld=-1.0, + wa_fld=0.0, + ) + S.write(fname_simparfile + "_pop{}.sbmy".format(i)) + elif m.group(1) == "splitLPT": + datadir = fsimdir + "/data/" + RedshiftLPT = eff_redshifts[0] + RedshiftFCs = eff_redshifts[0] + + # Write the parameter file for the first simulation + S = param_file( + ################ + ## Module LPT ## + ################ + ModuleLPT=1, + # Basic setup: + Particles=Np0, + Mesh=size, + BoxSize=L, + corner0=0.0, + corner1=0.0, + corner2=0.0, + # Initial conditions: + ICsMode=1, + WriteICsRngState=0, + InputWhiteNoise=fname_whitenoise, + OutputInitialConditions=fname_outputinitialdensity, + InputPowerSpectrum=fname_power_spectrum, + # Final conditions for LPT: + RedshiftLPT=RedshiftLPT, + WriteLPTSnapshot=0, + WriteLPTDensity=0, + # Final snapshot: + RedshiftFCs=RedshiftFCs, + WriteFinalDensity=0, + WriteLPTDisplacements=0, + ######### + ## RSD ## + ######### + ModuleRSD=1, + WriteIntermediaryRSD=0, + DoNonLinearMapping=NonLinearRSD, + WriteRSDensity=1, + OutputRSDensity=fnames_outputdensity[0], + ############################# + ## Cosmological parameters ## + ############################# + h=h, + Omega_q=1.0 - Omega_m, + Omega_b=Omega_b, + Omega_m=Omega_m, + Omega_k=0.0, + n_s=nS, + sigma8=sigma8, + w0_fld=-1.0, + wa_fld=0.0, + ) + S.write(fname_simparfile + "_pop0.sbmy") + + for i in range(1, Ntimesteps): + RedshiftLPT = eff_redshifts[i] + RedshiftFCs = eff_redshifts[i] + + S = param_file( + ################ + ## Module LPT ## + ################ + ModuleLPT=1, + # Basic setup: + Particles=Np0, + Mesh=size, + BoxSize=L, + corner0=0.0, + corner1=0.0, + corner2=0.0, + # Initial conditions: + ICsMode=1, + WriteICsRngState=0, + InputWhiteNoise=fname_whitenoise, + OutputInitialConditions=fname_outputinitialdensity, + InputPowerSpectrum=fname_power_spectrum, + # Final conditions for LPT: + RedshiftLPT=RedshiftLPT, + WriteLPTDensity=0, + WriteLPTDisplacements=0, + ######### + ## RSD ## + ######### + ModuleRSD=1, + WriteIntermediaryRSD=0, + DoNonLinearMapping=NonLinearRSD, + WriteRSDensity=1, + OutputRSDensity=fnames_outputdensity[i], + ############################# + ## Cosmological parameters ## + ############################# + h=h, + Omega_q=1.0 - Omega_m, + Omega_b=Omega_b, + Omega_m=Omega_m, + Omega_k=0.0, + n_s=nS, + sigma8=sigma8, + w0_fld=-1.0, + wa_fld=0.0, + ) + S.write(fname_simparfile + "_pop{}.sbmy".format(i)) + + +def handle_time_stepping( + aa: List[float], + total_steps: int, + modeldir: str, + figuresdir: str, + sim_params: str, + force: bool = False, +) -> Tuple[Optional[List[int]], Optional[float]]: + """ + Create and merge individual time-stepping objects. + + Parameters + ---------- + aa : list of float + List of scale factors in ascending order. + total_steps : int + Total number of time steps to distribute among the provided + scale factors. + modeldir : str + Directory path to store generated time-stepping files. + figuresdir : str + Directory path to store time-stepping plots. + sim_params : str + Simulation parameter string (e.g., "custom", "std", "nograv"). + force : bool, optional + Whether to force recompute the time-stepping files. Default is + False. + + Returns + ------- + merged_path : str + Path to the merged time-stepping file. + indices_steps_cumul : list of int or None + Cumulative indices for the distributed steps. Returns None if + using splitLPT or if `sim_params` indicates an alternative + strategy. + eff_redshifts : float or None + Effective redshift derived from the final scale factor in + 'custom' or 'nograv' mode. None otherwise. + + Raises + ------ + NotImplementedError + If a unsupported time-stepping strategy is used. + OSError + If file or directory operations fail. + RuntimeError + If unexpected issues occur during time-stepping setup. + """ + import numpy as np + + from pysbmy.timestepping import StandardTimeStepping, read_timestepping + from selfisys.utils.plot_utils import reset_plotting, setup_plotting + from selfisys.utils.timestepping import merge_nTS + + logger.info("Evaluating time-stepping strategy: %s", sim_params) + + indices_steps_cumul = None + eff_redshifts = None + + isstd = sim_params.startswith("std") + splitLPT = sim_params.startswith("splitLPT") + + try: + # Case 1: standard approach with distributed steps + if not isstd and not splitLPT: + reset_plotting() # Revert to default plotting style + merged_path = modeldir + "merged.h5" + + # Create time-stepping + if not os.path.exists(merged_path) or force: + logger.info("Setting up time-stepping...") + + # Distribute steps among the scale factors + nsteps = [ + round((aa[i + 1] - aa[i]) / (aa[-1] - aa[0]) * total_steps) + for i in range(len(aa) - 1) + ] + # Adjust the largest gap if rounding caused a mismatch + if sum(nsteps) != total_steps: + nsteps[nsteps.index(max(nsteps))] += total_steps - sum(nsteps) + + indices_steps_cumul = list(np.cumsum(nsteps) - 1) + np.save(modeldir + "indices_steps_cumul.npy", indices_steps_cumul) + + INDENT() + logger.diagnostic("Generating individual time-stepping objects...") + + TS_paths = [] + for i, (ai, af) in enumerate(zip(aa[:-1], aa[1:])): + snapshots = np.full((nsteps[i]), False) + snapshots[-1] = True # Mark last step as a snapshot + TS = StandardTimeStepping(ai, af, snapshots, 0) + TS_path = modeldir + f"ts{i+1}.h5" + TS.write(str(TS_path)) + TS_paths.append(TS_path) + + # Ensure the timestepping object are readable and plot + for i, path_ts in enumerate(TS_paths): + read_timestepping(str(path_ts)).plot(path=str(figuresdir + f"TS{i}.png")) + logger.diagnostic("Generating individual time-stepping objects done.") + + logger.diagnostic("Merging time-stepping...") + merge_nTS([str(p) for p in TS_paths], merged_path) + TS_merged = read_timestepping(merged_path) + TS_merged.plot(path=str(figuresdir + "TS_merged.png")) + + # Restore the project's plotting style + setup_plotting() + logger.diagnostic("Merging time-stepping done.") + UNINDENT() + logger.info("Setting up time-stepping done.") + else: + logger.diagnostic("Time-stepping objects already computed.") + + # Evaluate final effective redshift + if sim_params.startswith("custom") or sim_params.startswith("nograv"): + eff_redshifts = 1 / aa[-1] - 1 + else: + raise NotImplementedError("Time-stepping strategy not yet implemented.") + + # Case 2: splitted + elif splitLPT: + indices_steps_cumul = [f"pop{i}" for i in range(1, len(aa))] + eff_redshifts = [1 / a - 1 for a in aa[1:]] + + # Case 3: other + else: + logger.diagnostic("Standard time-stepping or no special distribution required.") + + except OSError as e: + logger.error("File or directory access error in handle_time_stepping: %s", str(e)) + raise + except Exception as e: + logger.critical("An error occurred during time-stepping setup: %s", str(e)) + raise RuntimeError("Time-stepping setup failed.") from e + finally: + gc.collect() + + return merged_path, indices_steps_cumul, eff_redshifts diff --git a/src/selfisys/selection_functions.py b/src/selfisys/selection_functions.py new file mode 100644 index 0000000..513d1e3 --- /dev/null +++ b/src/selfisys/selection_functions.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +"""Selection functions to simulate galaxy populations. +""" + +import os +from gc import collect +import numpy as np +import h5py + + +class LognormalSelection: + """Class to generate radial selection functions.""" + + def __init__( + self, + L=None, + selection_params=None, + survey_mask_path=None, + local_select_path=None, + size=None, + ): + """ + Initialise the LognormalSelection object. + + Parameters + ---------- + L : float + Size of the simulation box (in Mpc/h). If not provided, it + must be set before calling init_selection and using + grid-dependent methods. + selection_params : tuple of arrays + Parameters for the selection functions (ss, mm, rr). + Required for calling init_selection. + survey_mask_path : str or None + Path to the survey mask file. Required for calling + init_selection. + local_select_path : str + Path where the selection function will be saved. Required + for calling init_selection. + size : int, optional + Number of grid points along each axis. If not provided, it + must be set before using grid-dependent methods. + """ + self.L = L + self.selection_params = selection_params + self.survey_mask_path = survey_mask_path + self.local_select_path = local_select_path + self.size = size + + def r_grid(self): + """Compute the grid of radial distances in the simulation box. + + Returns + ------- + ndarray + 3D array of radial distances from the origin. + + Raises + ------ + AttributeError + If the 'size' attribute is not defined. + """ + if self.size is None: + raise AttributeError( + "The attribute 'size' must be defined to compute the radial grid." + ) + if self.L is None: + raise AttributeError("The attribute 'L' must be defined to compute the radial grid.") + + range1d = np.linspace(0, self.L, self.size, endpoint=False) + xx, yy, zz = np.meshgrid(range1d, range1d, range1d) + x0 = y0 = z0 = 0.0 + r = np.sqrt((xx - x0) ** 2 + (yy - y0) ** 2 + (zz - z0) ** 2) + 1e-10 + return r + + @staticmethod + def one_lognormal(x, std, mean, rescale=None): + """Rescaled log-normal distribution. + + Parameters + ---------- + x : ndarray + Input array. + std : float + Standard deviation of the distribution. + mean : float + Mean of the distribution. + rescale : float, optional + Rescaling factor. If None, the distribution is normalised + such that its maximum value is 1. + + Returns + ------- + ndarray + Log-normal distribution evaluated at x. + """ + mu = np.log(mean**2 / np.sqrt(std**2 + mean**2)) + sig2 = np.log(1 + std**2 / mean**2) + lognorm = (1 / (np.sqrt(2 * np.pi) * np.sqrt(sig2) * x)) * np.exp( + -((np.log(x) - mu) ** 2 / (2 * sig2)) + ) + if rescale is None: + return lognorm / np.max(lognorm) + else: + return lognorm * rescale + + def multiple_lognormal(self, x, mask, ss, ll, rr): + """Compute multiple log-normal distributions. + + Parameters + ---------- + x : ndarray + Input array. + mask : ndarray or None + Survey mask C(n). + ss : array_like + Standard deviations for each distribution. + ll : array_like + Means for each distribution. + rr : array_like + Rescaling factors for each distribution. + + Returns + ------- + list of ndarray + List of log-normal distributions. + """ + if mask is None: + mask = np.ones_like(x) + return [self.one_lognormal(x, s, l, r) * mask for s, l, r in zip(ss, ll, rr)] + + @staticmethod + def one_lognormal_z(x, sig2, mu, rescale=None): + """Compute a log-normal distribution in redshift. + + Parameters + ---------- + x : ndarray + Input array. + sig2 : float + Variance of the distribution. + mu : float + Mean of the distribution. + rescale : float, optional + Rescaling factor. + + Returns + ------- + ndarray + Log-normal distribution evaluated at x. + """ + lognorm = (1 / (np.sqrt(2 * np.pi) * np.sqrt(sig2) * x)) * np.exp( + -((np.log(x) - mu) ** 2 / (2 * sig2)) + ) + return lognorm * rescale if rescale is not None else lognorm + + def multiple_lognormal_z(self, x, mask, ss, mm, rr): + """ + Compute multiple rescaled lognormal distributions as functions + of redshift. + + Parameters + ---------- + x : ndarray + Input array (redshifts). + mask : ndarray or None + Survey mask C(n). + ss : array_like + Standard deviations of the lognormal distributions. + mm : array_like + Means of the lognormal distributions. + rr : array_like + Rescaling factors for each distribution. + + Returns + ------- + list of ndarray + List of log-normal distributions. + """ + if mask is None: + mask = np.ones_like(x) + res = [] + maxima = [] + for s, m, r in zip(ss, mm, rr): + mu = np.log(m**2 / np.sqrt(s**2 + m**2)) + sig2 = np.log(1 + s**2 / m**2) + maxima.append(np.exp(sig2 / 2 - mu) / (np.sqrt(2 * np.pi * sig2))) + res.append(self.one_lognormal_z(x, sig2, mu, rescale=r) * mask) + max = np.max(maxima) + res = [r / max for r in res] + return res + + def lognormals_z_to_x(self, xx, mask, params, spline): + """Convert log-normal distributions from redshift to distance. + + Parameters + ---------- + xx : array-like + Comoving distances at which to evaluate the distributions. + mask : ndarray or None + Survey mask C(n). + params : tuple of arrays + Parameters for the distributions (ss, mm, rr). + spline : UnivariateSpline + Linear interpolator for the distance-redshift relation. + + Returns + ------- + tuple + Tuple containing redshifts and list of distributions. + """ + ss, mm, rr = params + zs = np.maximum(1e-4, spline(xx)) + res = self.multiple_lognormal_z(zs, mask, ss, mm, rr) + return zs, res + + def init_selection(self, reset=False): + """Initialise the radial selection functions. + + Parameters + ---------- + reset : bool, optional + Whether to reset the selection function. + + Raises + ------ + + """ + if any([self.survey_mask_path is None, self.local_select_path is None]): + raise AttributeError( + "Some attributes are missing to initialise the selection function." + ) + + if not os.path.exists(self.local_select_path) or reset: + from scipy.interpolate import UnivariateSpline + from classy import Class + from astropy.cosmology import FlatLambdaCDM + from selfisys.utils.tools import cosmo_vector_to_class_dict + from selfisys.global_parameters import omegas_gt + from selfisys.utils.plot_utils import plot_selection_functions + + # Redshift-distance relation + redshifts_upper_bound = 3.0 + zz = np.linspace(0, redshifts_upper_bound, 10_000) + cosmo = FlatLambdaCDM(H0=100 * omegas_gt[0], Ob0=omegas_gt[1], Om0=omegas_gt[2]) + d = cosmo.comoving_distance(zz).value / 1e3 # -> Gpc/h + spline = UnivariateSpline(d, zz, k=1, s=0) + + # Plot the selection functions + L = self.L / 1e3 + Lcorner = np.sqrt(3) * L + zcorner = zz[np.argmin(np.abs(d - Lcorner))] + + # Get linear growth factor from CLASS + cosmo_dict = cosmo_vector_to_class_dict(omegas_gt) + cosmo_class = Class() + cosmo_class.set(cosmo_dict) + cosmo_class.compute() + Dz = cosmo_class.get_background()["gr.fac. D"] + redshifts = cosmo_class.get_background()["z"] + cosmo_class.struct_cleanup() + cosmo_class.empty() + + # Define the axis for the plot + xx = np.linspace(1e-5, Lcorner, 1000) + zz, res = self.lognormals_z_to_x( + xx, + None, + self.selection_params, + spline, + ) + + # Call auxiliary plotting routine + plot_selection_functions( + xx, + res, + None, + self.selection_params, + L, + np.sqrt(3) * L, + zz=zz, + zcorner=zcorner, + path=self.local_select_path[:-3] + ".png", + ) + + # Compute the selection function and save it to disk + survey_mask = np.load(self.survey_mask_path) if self.survey_mask_path else None + r = self.r_grid() / 1e3 # Convert to Gpc/h + _, select_fct = self.lognormals_z_to_x(r, survey_mask, self.selection_params, spline) + with h5py.File(self.local_select_path, "w") as f: + f.create_dataset("select_fct", data=select_fct) + + del survey_mask, r, d, zz, spline, select_fct + collect() diff --git a/src/selfisys/selfi_interface.py b/src/selfisys/selfi_interface.py new file mode 100644 index 0000000..499f807 --- /dev/null +++ b/src/selfisys/selfi_interface.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Provides simple wrappers around pyselfi.utils functions for the SelfiSys +pipeline. +""" + + +def PrintMessage(required_verbosity: int, message: str, verbosity: int) -> None: + """ + Print a message to standard output using pyselfi.utils.PrintMessage. + + Parameters + ---------- + required_verbosity : int + The verbosity level required to display the message. + message : str + The actual message to display. + verbosity : int + The current verbosity level (0=quiet, 1=normal, 2=debug). + """ + from pyselfi.utils import PrintMessage as PSMessage + + if verbosity >= required_verbosity: + PSMessage(3, message) + + +def indent() -> None: + """Indent the standard output using pyselfi.utils.""" + from pyselfi.utils import INDENT + + INDENT() + + +def unindent() -> None: + """Unindent the standard output using pyselfi.utils.""" + from pyselfi.utils import UNINDENT + + UNINDENT() diff --git a/src/selfisys/setup_model.py b/src/selfisys/setup_model.py new file mode 100644 index 0000000..a0eddc4 --- /dev/null +++ b/src/selfisys/setup_model.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Set up parameters related to the grid and the fiducial power spectrum. +""" + +import os.path +import gc +from typing import Optional, NamedTuple +import numpy as np +from h5py import File + +from pysbmy.power import PowerSpectrum, FourierGrid, get_Pk +from selfisys.utils.logger import getCustomLogger +from selfisys.utils.tools import get_k_max + +logger = getCustomLogger(__name__) + + +class ModelSetup(NamedTuple): + size: int + L: float + P: int + S: int + G_sim_path: str + G_ss_path: str + Pbins_bnd: np.ndarray + Pbins: np.ndarray + k_s: np.ndarray + P_ss_obj_path: str + P_0: np.ndarray + planck_Pk: np.ndarray + + +def setup_model( + workdir: str, + params_planck: dict, + params_P0: dict, + size: int = 256, + L: float = 3600.0, + S: int = 100, + N_exact: int = 8, + Pinit: int = 50, + trim_threshold: int = 100, + minval: Optional[float] = None, + maxval: Optional[float] = None, + force: bool = False, +) -> ModelSetup: + """ + Set up the model by computing or loading necessary grids and + parameters. + + Parameters + ---------- + workdir : str + Directory where the results will be stored. + params_planck : dict + Parameters for the Planck 2018 cosmology. + params_P0 : dict + Parameters for the normalisation power spectrum. + size : int + Number of elements in each direction of the box. + L : float + Comoving length of the box in Mpc/h. + S : int + Number of support wavenumbers for the input power spectra. + N_exact : int + Number of support wavenumbers matching the Fourier grid. + Pinit : int + Maximum number of bins for the summaries. + trim_threshold : int + Minimum number of modes required per bin. + minval : float, optional + Minimum k value for the summaries. + maxval : float, optional + Maximum k value for the summaries. + force : bool + If True, forces recomputation of the inputs. + + Returns + ------- + ModelSetup + A named tuple containing: + - size (int): Number of elements in each direction of the box. + - L (float): Comoving length of the box in Mpc/h. + - P (int): Number of bins for the summaries. + - S (int): Number of support wavenumbers for input powerspectra. + - G_sim_path (str): Path to the full Fourier grid file. + - G_ss_path (str): Path to the Fourier grid for summaries file. + - Pbins_bnd (np.ndarray): Boundaries of summary bins. + - Pbins (np.ndarray): Centres of the bins for the summaries. + - k_s (np.ndarray): Support wavenumbers for input power spectra. + - P_ss_obj_path (str): Path to the summary power spectrum file. + - P_0 (np.ndarray): Normalisation power spectrum values. + - planck_Pk (np.ndarray): Planck 2018 power spectrum values. + """ + # Check input parameters + if N_exact < 0 or N_exact > S: + raise ValueError("Parameter 'N_exact' must be between 0 and 'S'.") + + # Define file paths + G_sim_path = os.path.join(workdir, "G_sim.h5") + k_s_path = os.path.join(workdir, "k_s.npy") + G_ss_path = os.path.join(workdir, "G_ss.h5") + P_ss_obj_path = os.path.join(workdir, "P_ss_obj.h5") + P_0_path = os.path.join(workdir, "P_0.npy") + theta_planck_path = os.path.join(workdir, "theta_planck.npy") + Pbins_path = os.path.join(workdir, "Pbins.npy") + Pbins_bnd_path = os.path.join(workdir, "Pbins_bnd.npy") + + # Compute or load the full Fourier grid + if not os.path.exists(G_sim_path) or force: + logger.info("Computing Fourier grid...") + G_sim = FourierGrid(L, L, L, size, size, size) + G_sim.write(G_sim_path) + logger.info("Computing Fourier grid done.") + else: + logger.info("Loading Fourier grid.") + G_sim = FourierGrid.read(G_sim_path) + + # Determine minimum and maximum k values + if minval is None: + minval = np.min(G_sim.k_modes[G_sim.k_modes != 0]) + if maxval is None: + maxval = np.pi * size / L # 1D Nyquist frequency + + # Compute or load support wavenumbers for the input power spectrum + if not os.path.exists(k_s_path) or force: + logger.diagnostic("Computing input power spectrum support wavenumbers...") + k_s = np.zeros(S) + sorted_knorms = np.sort(G_sim.k_modes.flatten()) + unique_indices = np.unique(np.round(sorted_knorms, 5), return_index=True)[1] + sorted_knorms_corrected = sorted_knorms[unique_indices] + k_s[:N_exact] = sorted_knorms_corrected[1 : N_exact + 1] + k_s_max = get_k_max(L, size) + k_s[N_exact:] = np.logspace( + np.log10(sorted_knorms_corrected[N_exact]), + np.log10(k_s_max), + S - N_exact + 1, + )[1:] + np.save(k_s_path, k_s) + logger.diagnostic("Computing input power spectrum support wavenumbers done.") + else: + logger.diagnostic("Loading input power spectrum support wavenumbers.") + try: + k_s = np.load(k_s_path) + except (IOError, FileNotFoundError) as e: + logger.error(f"Failed to load k_s from {k_s_path}: {e}") + raise + + # Initialise Pbins + Pbins_left_bnds_init = np.logspace( + np.log10(minval), np.log10(maxval), Pinit + 1, dtype=np.float32 + ) + Pbins_left_bnds_init = Pbins_left_bnds_init[:-1] + + # Compute or load Fourier grid for the summaries + if not os.path.exists(G_ss_path) or force: + G_ss = FourierGrid( + L, + L, + L, + size, + size, + size, + k_modes=Pbins_left_bnds_init, + kmax=maxval, + trim_bins=True, + trim_threshold=trim_threshold, + ) + G_ss.write(G_ss_path) + else: + G_ss = FourierGrid.read(G_ss_path) + P = G_ss.NUM_MODES + + # Compute or load Pbins and Pbins_bnd + if not os.path.exists(Pbins_path) or not os.path.exists(Pbins_bnd_path) or force: + k_ss_max_offset = Pbins_left_bnds_init[-1] - Pbins_left_bnds_init[-2] + logger.diagnostic(f"k_ss_max_offset: {k_ss_max_offset:.5f}") + Pbins_bnd = G_ss.k_modes + Pbins_bnd = np.concatenate([Pbins_bnd, [Pbins_bnd[-1] + k_ss_max_offset]]) + Pbins = (Pbins_bnd[1:] + Pbins_bnd[:-1]) / 2 + np.save(Pbins_path, Pbins) + np.save(Pbins_bnd_path, Pbins_bnd) + else: + try: + Pbins = np.load(Pbins_path) + Pbins_bnd = np.load(Pbins_bnd_path) + except (IOError, FileNotFoundError) as e: + logger.error(f"Failed to load Pbins or Pbins_bnd: {e}") + raise + + # Compute or load BBKS spectrum for normalisation + if not os.path.exists(P_0_path) or force: + P_0 = get_Pk(k_s, params_P0) + np.save(P_0_path, P_0) + else: + try: + P_0 = np.load(P_0_path) + except (IOError, FileNotFoundError) as e: + logger.error(f"Failed to load P_0 from {P_0_path}: {e}") + raise + + if not os.path.exists(P_ss_obj_path) or force: + P_0_ss = get_Pk(G_ss.k_modes, params_P0) + P_ss_obj = PowerSpectrum.from_FourierGrid(G_ss, powerspectrum=P_0_ss, cosmo=params_P0) + P_ss_obj.write(P_ss_obj_path) + else: + P_ss_obj = PowerSpectrum.read(P_ss_obj_path) + + # Compute or load Planck power spectrum + if not os.path.exists(theta_planck_path) or force: + planck_Pk = get_Pk(k_s, params_planck) + np.save(theta_planck_path, planck_Pk) + else: + try: + planck_Pk = np.load(theta_planck_path) + except (IOError, FileNotFoundError) as e: + logger.error(f"Failed to load theta_planck from {theta_planck_path}: {e}") + raise + + # Clean up + del G_sim, G_ss, P_ss_obj, Pbins_left_bnds_init + gc.collect() + + return ModelSetup( + size, + L, + P, + S, + G_sim_path, + G_ss_path, + Pbins_bnd, + Pbins, + k_s, + P_ss_obj_path, + P_0, + planck_Pk, + ) + + +def compute_alpha_cv( + workdir: str, + k_s: np.ndarray, + size: int, + L: float, + window_fct_path: Optional[str] = None, + force: bool = False, +) -> None: + """ + Compute the cosmic variance parameter alpha_cv. + + Parameters + ---------- + workdir : str + Directory where the results will be stored. + k_s : np.ndarray + Support wavenumbers. + size : int + Number of elements in each direction of the box. + L : float + Comoving length of the box in Mpc/h. + window_fct_path : str, optional + Path to the window function file. + force : bool + If True, forces recomputation of the inputs. + + """ + from scipy.optimize import curve_fit + + alpha_cv_path = os.path.join(workdir, "alpha_cv.npy") + alpha_cv_eff_path = os.path.join(workdir, "alpha_cv_eff.npy") + + if not os.path.exists(alpha_cv_path) or force: + logger.info("Computing cosmic variance alpha_cv...") + k_s_bnd = np.concatenate([k_s, [np.inf]]) + + G_sim = FourierGrid.read(os.path.join(workdir, "G_sim.h5")).k_modes.flatten() + knorms = np.sort(G_sim) + + Nks, _ = np.histogram(knorms, bins=k_s_bnd) + + del knorms, G_sim + + nyquist_frequency = np.pi * size / L + idx_nyquist = np.searchsorted(k_s, nyquist_frequency) + + def cubic_func(x, a): + return a * x**3 + + try: + popt, _ = curve_fit(cubic_func, k_s[:idx_nyquist], Nks[:idx_nyquist]) + except RuntimeError as e: + logger.error(f"Curve fitting failed: {e}") + raise + + alpha_cv = np.sqrt(1 / popt[0]) + np.save(alpha_cv_path, alpha_cv) + logger.info(f"Computing cosmic variance alpha_cv done. alpha_cv = {alpha_cv}") + + if window_fct_path is not None: + # Compute alpha_cv with approximate correction for the effective volume + nnz = 0 + with File(window_fct_path, "r") as f: + for ipop in range(3): + mask = f["select_fct"][:][ipop] + nnz += np.sum(mask) + nnz_size = nnz ** (1 / 3.0) # Side length of a cube containing nnz voxels + eff_L = nnz_size * L / size + + alpha_cv_eff = alpha_cv * (L / eff_L) ** 1.5 + logger.info(f"Effective length: {eff_L * 1e-3} Gpc/h") + logger.info(f"Effective volume: {(eff_L * 1e-3) ** 3} (Gpc/h)^3") + logger.info(f"alpha_cv_eff = {alpha_cv_eff}") + np.save(alpha_cv_eff_path, alpha_cv_eff) + + gc.collect() diff --git a/src/selfisys/utils/__init__.py b/src/selfisys/utils/__init__.py new file mode 100644 index 0000000..5e681d3 --- /dev/null +++ b/src/selfisys/utils/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + + +"""Utility functions for the SelfiSys pipeline. +""" diff --git a/src/selfisys/utils/examples_utils.py b/src/selfisys/utils/examples_utils.py new file mode 100644 index 0000000..e239392 --- /dev/null +++ b/src/selfisys/utils/examples_utils.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +This module provides utility functions for the examples. +""" + + +def clear_large_plot(fig): + """ + Clear a figure to free up memory. + + Parameters + ---------- + fig : matplotlib.figure.Figure + The figure to clear. + """ + from IPython.display import clear_output + + del fig + clear_output() diff --git a/src/selfisys/utils/logger.py b/src/selfisys/utils/logger.py new file mode 100644 index 0000000..d84589f --- /dev/null +++ b/src/selfisys/utils/logger.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Logger routines for the SelfiSys package. + +The printing routines and colours are adapted from the Simbelmynë +comological solver (https://simbelmyne.readthedocs.io/en/latest). +""" + +import sys +from typing import cast +import logging +from selfisys import DEFAULT_VERBOSE_LEVEL + +# Global variables for fonts +FONT_BOLDRED = "\033[1;31m" +FONT_BOLDGREEN = "\033[1;32m" +FONT_BOLDYELLOW = "\033[1;33m" +FONT_BOLDCYAN = "\033[1;36m" +FONT_BOLDGREY = "\033[1;37m" +FONT_LIGHTPURPLE = "\033[38;5;147m" + +FONT_NORMAL = "\033[00m" + +# Global variables for verbosity +ERROR_VERBOSITY = 0 +INFO_VERBOSITY = 1 +WARNING_VERBOSITY = 2 +DIAGNOSTIC_VERBOSITY = 3 +DEBUG_VERBOSITY = 4 +DIAGNOSTIC_LEVEL = 15 +logging.addLevelName(DIAGNOSTIC_LEVEL, "DIAGNOSTIC") + +G__ind__ = 0 # Global variable for logger indentation + + +def INDENT(): + """Indents the current level of outputs.""" + global G__ind__ + G__ind__ += 1 + return G__ind__ + + +def UNINDENT(): + """Unindents the current level of outputs.""" + global G__ind__ + G__ind__ -= 1 + return G__ind__ + + +def PrintLeftType(message_type, FONT_COLOR): + """Prints the type of output to screen. + + Parameters + ---------- + message_type (string) : type of message + FONT_COLOR (string) : font color for this type of message + + """ + from time import localtime, strftime + + sys.stdout.write( + "[" + + strftime("%H:%M:%S", localtime()) + + "|" + + FONT_COLOR + + message_type + + FONT_NORMAL + + "]" + ) + sys.stdout.write("==" * G__ind__) + sys.stdout.write("|") + + +def PrintInfo(message): + """Prints an information to screen. + + Parameters + ---------- + message (string) : message + + """ + if DEFAULT_VERBOSE_LEVEL >= INFO_VERBOSITY: + PrintLeftType("INFO ", FONT_BOLDCYAN) + sys.stdout.write("{}\n".format(message)) + sys.stdout.flush() + + +def PrintDiagnostic(verbosity, message): + """Prints a diagnostic to screen. + + Parameters + ---------- + verbosity (int) : verbosity of the message + message (string) : message + + """ + if DEFAULT_VERBOSE_LEVEL >= verbosity: + PrintLeftType("DIAGNOSTIC", FONT_BOLDGREY) + sys.stdout.write("{}\n".format(message)) + + +def PrintWarning(message): + """Prints a warning to screen. + + Parameters + ---------- + message (string) : message + + """ + if DEFAULT_VERBOSE_LEVEL >= WARNING_VERBOSITY: + PrintLeftType("WARNING ", FONT_BOLDYELLOW) + sys.stdout.write(FONT_BOLDYELLOW + message + FONT_NORMAL + "\n") + + +def PrintError(message): + """Prints an error to screen. + + Parameters + ---------- + message (string) : message + + """ + if DEFAULT_VERBOSE_LEVEL >= ERROR_VERBOSITY: + PrintLeftType("ERROR ", FONT_BOLDRED) + sys.stdout.write(FONT_BOLDRED + message + FONT_NORMAL + "\n") + + +class CustomLoggerHandler(logging.Handler): + """ + Custom logging handler to redirect Python logger messages to custom + print functions, with support for verbosity levels in debug + messages. + """ + + def emit(self, record): + """ + Emit a log record. + """ + try: + log_message = self.format(record) + log_level = record.levelno + + if log_level >= logging.ERROR: + PrintError(log_message) + elif log_level >= logging.WARNING: + PrintWarning(log_message) + elif log_level >= logging.INFO: + PrintInfo(log_message) + elif log_level == DIAGNOSTIC_LEVEL: + # Retrieve verbosity level from the record + verbosity = getattr(record, "verbosity", DIAGNOSTIC_VERBOSITY) + PrintDiagnostic(verbosity=verbosity, message=log_message) + elif log_level >= logging.DEBUG: + PrintDiagnostic(verbosity=DEBUG_VERBOSITY, message=log_message) + else: + # Fallback for other levels + PrintInfo(log_message) + except Exception: + self.handleError(record) + + +class CustomLogger(logging.Logger): + """ + Custom logger class supporting custom verbosity levels in diagnostic + messages. + """ + + def diagnostic(self, msg, *args, verbosity=DIAGNOSTIC_VERBOSITY, **kwargs) -> None: + """ + Log a message with DIAGNOSTIC level. + + Parameters + ---------- + msg : str + The message to log. + verbosity : int, optional + The verbosity level required to log this message. + """ + if self.isEnabledFor(DIAGNOSTIC_LEVEL): + # Pass verbosity as part of the extra argument + extra = kwargs.get("extra", {}) + extra["verbosity"] = verbosity + kwargs["extra"] = extra + self.log(DIAGNOSTIC_LEVEL, msg, *args, **kwargs) + + +logging.setLoggerClass(CustomLogger) + + +def getCustomLogger(name: str) -> CustomLogger: + """ + Get as CustomLogger instance to use the custom printing routines. + + Parameters + ---------- + name : str + The name of the logger. + + Returns + ------- + logger : logging.Logger + The custom logger instance. + """ + logging.setLoggerClass(CustomLogger) + logger = cast(CustomLogger, logging.getLogger(name)) # cast for type checkers and PyLance + logger.setLevel(logging.DEBUG) # Set the desired base logging level + + handler = CustomLoggerHandler() + formatter = logging.Formatter(f"{FONT_LIGHTPURPLE}(%(name)s){FONT_NORMAL} %(message)s") + handler.setFormatter(formatter) + + # Attach the handler to the logger if not already present + if not logger.handlers: + logger.addHandler(handler) + + return logger diff --git a/src/selfisys/utils/low_level.py b/src/selfisys/utils/low_level.py new file mode 100644 index 0000000..2b437c5 --- /dev/null +++ b/src/selfisys/utils/low_level.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Tools to deal with low-level operations such as redirecting stdout from +C code. +""" + +from contextlib import contextmanager +import platform +import ctypes +import io +import os, sys +import tempfile + +libc = ctypes.CDLL(None) +if platform.system() == "Darwin": # macOS + stdout_symbol = "__stdoutp" + stderr_symbol = "__stderrp" +else: + stdout_symbol = "stdout" + stderr_symbol = "stderr" +c_stdout = ctypes.c_void_p.in_dll(libc, stdout_symbol) +c_stderr = ctypes.c_void_p.in_dll(libc, stderr_symbol) + + +# Taken from: +# https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/ +@contextmanager +def stdout_redirector(stream): + """A context manager that redirects stdout to the given stream. For + instance, this can be used to redirect C code stdout to None (to + avoid cluttering the log, e.g., when using tqdm). + + Args: + stream (file-like object): The stream to which stdout should be + redirected. + Example: + >>> with stdout_redirector(stream): + >>> print("Hello world!") # Will be printed to stream + >>> # instead of stdout. + """ + # The original fd stdout points to. Usually 1 on POSIX systems. + original_stdout_fd = sys.stdout.fileno() + + def _redirect_stdout(to_fd): + """Redirect stdout to the given file descriptor.""" + # Flush the C-level buffer stdout + libc.fflush(c_stdout) + # Flush and close sys.stdout - also closes the file descriptor (fd) + sys.stdout.close() + # Make original_stdout_fd point to the same file as to_fd + os.dup2(to_fd, original_stdout_fd) + # Create a new sys.stdout that points to the redirected fd + sys.stdout = io.TextIOWrapper(os.fdopen(original_stdout_fd, "wb")) + + # Save a copy of the original stdout fd in saved_stdout_fd + saved_stdout_fd = os.dup(original_stdout_fd) + try: + # Create a temporary file and redirect stdout to it + tfile = tempfile.TemporaryFile(mode="w+b") + _redirect_stdout(tfile.fileno()) + # Yield to caller, then redirect stdout back to the saved fd + yield + _redirect_stdout(saved_stdout_fd) + # Copy contents of temporary file to the given stream + tfile.flush() + tfile.seek(0, io.SEEK_SET) + stream.write(tfile.read()) + finally: + tfile.close() + os.close(saved_stdout_fd) + + +# Adapted from: +# https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/ +@contextmanager +def stderr_redirector(stream): + """A context manager that redirects stderr to the given stream. + For instance, this can be used to redirect C code stderr to None (to + avoid cluttering the log, e.g., when using tqdm). + + Use with caution. + + Args: + stream (file-like object): The stream to which stdout should be + redirected. + """ + # The original fd stdout points to. Usually 1 on POSIX systems. + original_stderr_fd = sys.stderr.fileno() + + def _redirect_stderr(to_fd): + """Redirect stderr to the given file descriptor.""" + # Flush the C-level buffer stderr + libc.fflush(c_stderr) + # Flush and close sys.stderr - also closes the file descriptor (fd) + sys.stderr.close() + # Make original_stderr_fd point to the same file as to_fd + os.dup2(to_fd, original_stderr_fd) + # Create a new sys.stderr that points to the redirected fd + sys.stderr = io.TextIOWrapper(os.fdopen(original_stderr_fd, "wb")) + + # Save a copy of the original stdout fd in saved_stdout_fd + saved_stderr_fd = os.dup(original_stderr_fd) + try: + # Create a temporary file and redirect stdout to it + tfile = tempfile.TemporaryFile(mode="w+b") + _redirect_stderr(tfile.fileno()) + # Yield to caller, then redirect stdout back to the saved fd + yield + _redirect_stderr(saved_stderr_fd) + # Copy contents of temporary file to the given stream + tfile.flush() + tfile.seek(0, io.SEEK_SET) + stream.write(tfile.read()) + finally: + tfile.close() + os.close(saved_stderr_fd) diff --git a/src/selfisys/utils/parser.py b/src/selfisys/utils/parser.py new file mode 100644 index 0000000..2310d8e --- /dev/null +++ b/src/selfisys/utils/parser.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +"""Utility functions for parsing command-line arguments. +""" + +import os +from argparse import ArgumentParser, ArgumentTypeError + + +def joinstrs(list_of_strs): + """Join a list of strings into a single string. + + Parameters + ---------- + list_of_strs : list of str + List of strings to join. + + Returns + ------- + str + Concatenated string. + """ + return "".join([str(x) for x in list_of_strs if x is not None]) + + +def joinstrs_only(list_of_strs): + """Join a list of strings into a single string, ignoring all + non-string elements such as None values. + + Parameters + ---------- + list_of_strs : list of str + List of strings to join. + + Returns + ------- + str + Concatenated string. + """ + return "".join([str(x) for x in list_of_strs if type(x) == str]) + + +def check_files_exist(files): + """Check if all files in the list exist. + + Parameters + ---------- + files : list of str + List of file paths to check. + + Returns + ------- + bool + True if all files exist, False otherwise. + """ + + return all(os.path.exists(f) for f in files) + + +def none_or_bool_or_str(value): + """Convert a string to None, bool, or str. + + Parameters + ---------- + value : str + String to convert. + + Returns + ------- + None, bool, or str + Converted value. + """ + if value == "None" or value == None: + return None + elif value == "True": + return True + elif value == "False": + return False + return value + + +def intNone(value): + """Convert a string to None or int. + + Parameters + ---------- + value : str + String to convert. + + Returns + ------- + None or int + Converted value. + """ + if value == "None" or value == None: + return None + else: + return int(value) + + +def safe_npload(path): + """Load a numpy array from a file. + + Parameters + ---------- + path : str + Path to the file to load. + + Returns + ------- + None or np.ndarray + Loaded array or None if the file does not exist. + """ + import numpy as np + + val = np.load(path, allow_pickle=True) + if val is None or val == "None" or val == None: + return None + else: + return val + + +def bool_sh(value): + """Convert a string to a boolean. + + Parameters + ---------- + value : str + String to convert. + + Returns + ------- + bool + Converted value. + """ + if value == "True": + return True + elif value == "False": + return False + else: + raise ArgumentTypeError("Boolean value expected.") diff --git a/src/selfisys/utils/path_utils.py b/src/selfisys/utils/path_utils.py new file mode 100644 index 0000000..306d2af --- /dev/null +++ b/src/selfisys/utils/path_utils.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +"""Helper functions to handle paths and file names. +""" + + +import os + + +def _get_prefix(prefix_mocks: str, suffix: str, sim_id=None, d=None, p=None) -> str: + """ + Get file prefix. + + Parameters + ---------- + prefix_mocks : str | None + Prefix for the mock data files. If None, defaults to base + suffix. + suffix : str + Base suffix for the file name (e.g., "mocks" or "g"). + sim_id : int, optional + Simulation ID. Used if d and p are not provided. + d : int, optional + Direction index. + p : int, optional + Simulation index. If both d and p are provided, they take + precedence over sim_id. + + Returns + ------- + str + Formatted file name string. + """ + prefix = f"{prefix_mocks}_{suffix}" if prefix_mocks else suffix + if d is not None and p is not None: + return f"{prefix}_d{d}_p{p}.h5" + return f"{prefix}_{sim_id}.h5" + + +def get_file_names( + fsimdir: str, + sim_id: int, + sim_params: str, + TimeSteps: list[int], + prefix_mocks: str, + gravity_on: bool = True, + return_g: bool = False, +) -> dict: + """ + Generate file paths for a given simulation ID and parameters. + + Parameters + ---------- + fsimdir : str + Path to the simulation directory. + sim_id : int + Simulation ID. + sim_params : str + Simulation parameters. + TimeSteps : list of int + List of time steps. + prefix_mocks : str | None + Prefix for mock data files. If None, defaults to "mocks". + gravity_on : bool, optional + Whether gravity is active. Default is True. + return_g : bool, optional + If True, return the file name for the observed galaxy field. + Default is False. + + Returns + ------- + dict + Dictionary containing simulation inputs / outputs file paths. + """ + datadir = os.path.join(fsimdir, "data") + names = { + "fname_cosmo": os.path.join(datadir, f"input_cosmo_{sim_id}.json"), + "fname_power_spectrum": os.path.join(datadir, f"input_power_{sim_id}.h5"), + "fname_outputinitialdensity": os.path.join(datadir, f"initial_density_{sim_id}.h5"), + "fname_mocks": os.path.join(datadir, _get_prefix(prefix_mocks, "mocks", sim_id)), + "fname_g": ( + os.path.join(datadir, _get_prefix(prefix_mocks, "g", sim_id)) if return_g else None + ), + "fname_simparfile": None, + "fname_whitenoise": None, + "seedname_whitenoise": None, + "fnames_outputLPTdensity": None, + "fnames_outputrealspacedensity": None, + "fnames_outputdensity": None, + "fname_simlogs": None, + } + + if gravity_on: + names.update( + { + "fname_simparfile": os.path.join(datadir, f"sim_{sim_id}"), + "fname_whitenoise": os.path.join( + datadir, f"initial_density_white_noise_{sim_id}.h5" + ), + "seedname_whitenoise": os.path.join(datadir, f"initial_density_wn_{sim_id}_seed"), + "fnames_outputLPTdensity": os.path.join(datadir, f"output_density_{sim_id}.h5"), + "fname_simlogs": os.path.join(datadir, f"logs_sim_{sim_id}.txt"), + } + ) + if sim_params.startswith(("split", "custom")): + names["fnames_outputdensity"] = [ + os.path.join(datadir, f"output_density_{sim_id}_{i}.h5") for i in TimeSteps[::-1] + ] + names["fnames_outputrealspacedensity"] = [ + os.path.join(datadir, f"output_realdensity_{sim_id}_{i}.h5") + for i in TimeSteps[::-1] + ] + else: + names["fnames_outputdensity"] = [os.path.join(datadir, f"output_density_{sim_id}.h5")] + names["fnames_outputrealspacedensity"] = [ + os.path.join(datadir, f"output_realdensity_{sim_id}.h5") + ] + + return names + + +def file_names_evaluate( + simdir: str, + sd: str, + d: int, + i: int, + sim_params: str, + TimeSteps: list[int], + prefix_mocks: str, + abc: bool = False, + gravity_on: bool = True, +) -> dict: + """ + Generate file paths for the given simulation id and parameters. + + Parameters + ---------- + simdir : str + Path to the simulation directory. + sd : str + Path to the simulation directory for the given direction. + d : int + Direction index (-1 for mock data, 0 for the expansion point, or + 1 to S). + i : int + Simulation index. + sim_params : str + Simulation parameters. + TimeSteps : list of int + List of time steps. + prefix_mocks : str | None + Prefix for mock data files. If None, defaults to "mocks". + abc : bool, optional + If True, appends the ABC index to the white noise path. + gravity_on : bool, optional + Whether gravity is active. Default is True. + + Returns + ------- + dict + Dictionary containing simulation inputs / outputs file paths. + """ + names = { + "fname_power_spectrum": os.path.join(sd, f"input_power_d{d}.h5"), + "fname_outputinitialdensity": os.path.join(sd, f"initial_density_d{d}_p{i}.h5"), + "fname_mocks": os.path.join(sd, _get_prefix(prefix_mocks, "mocks", d=d, p=i)), + "fname_simlogs": os.path.join(sd, f"logs_sim_d{d}_p{i}.txt"), + "fname_simparfile": None, + "fname_whitenoise": None, + "seedname_whitenoise": None, + "fnames_outputLPTdensity": None, + "fnames_outputrealspacedensity": None, + "fnames_outputdensity": None, + "fname_g": None, + } + + if gravity_on: + dir_wn = os.path.join(simdir, "..", "wn") if not abc else os.path.join(simdir, "wn", abc) + names.update( + { + "fname_simparfile": os.path.join(sd, f"sim_d{d}_p{i}"), + "fname_whitenoise": os.path.join(dir_wn, f"initial_density_white_p{i}.h5"), + "seedname_whitenoise": os.path.join(dir_wn, f"initial_density_white_p{i}"), + "fnames_outputLPTdensity": os.path.join(sd, f"output_density_d{d}_p{i}.h5"), + } + ) + if sim_params.startswith(("split", "custom")): + names["fnames_outputrealspacedensity"] = [ + os.path.join(sd, f"output_realdensity_d{d}_p{i}_{j}.h5") for j in TimeSteps[::-1] + ] + names["fnames_outputdensity"] = [ + os.path.join(sd, f"output_density_d{d}_p{i}_{j}.h5") for j in TimeSteps[::-1] + ] + else: + names["fnames_outputrealspacedensity"] = [ + os.path.join(sd, f"output_realdensity_d{d}_p{i}.h5") + ] + names["fnames_outputdensity"] = [os.path.join(sd, f"output_density_d{d}_p{i}.h5")] + + return names diff --git a/src/selfisys/utils/plot_examples.py b/src/selfisys/utils/plot_examples.py new file mode 100644 index 0000000..6c98be0 --- /dev/null +++ b/src/selfisys/utils/plot_examples.py @@ -0,0 +1,616 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +"""Visualisation utilities for the exploratory examples in SelfiSys. +""" + +import numpy as np +import matplotlib.pyplot as plt +from selfisys.utils.plot_params import * + +# Configure global plotting settings +setup_plotting() + + +def plot_power_spectrum( + G_sim, true_P, k_s, planck_Pk, Pbins, Pbins_bnd, size, L, wd, title=None, display=True +): + """ + Plot a power spectrum over Fourier modes, its linear interpolation + over specified support points, and a given binning for comparison. + + Parameters + ---------- + G_sim : pysbmy.power.FourierGrid + Fourier grid object containing the `k_modes` attribute. + true_P : pysbmy.power.PowerSpectrum + Power spectrum object containing the `powerspectrum` attribute. + k_s : array-like + Support points in k-space. + planck_Pk : array-like + Power spectrum values at the support points. + Pbins : array-like + Centres of the Φ bins in k-space. + Pbins_bnd : array-like + Boundaries of the Φ bins in k-space. + size : float + Box size in number of grid cells. + L : float + Box length in Mpc/h. + wd : str + Working directory path for saving the figure. + title : str, optional + Title for the figure. Default is None. + display : bool, optional + Whether to display the figure. Default is True. + + Returns + ------- + None + """ + import os + from selfisys.utils.logger import PrintInfo + + plt.figure(figsize=(15, 5)) + + # Plot power spectrum data + plt.plot(G_sim.k_modes, true_P.powerspectrum, label=r"$P(k)$ (over all modes)") + plt.plot(k_s, planck_Pk, label=r"$P(k)$ (binned–linear interpolation)", linestyle="dashed") + + # Configure axes + plt.xlabel(r"$k\,[h/\mathrm{Mpc}]$") + plt.ylabel(r"$[{\rm Mpc}/h]^3$") + plt.xscale("log") + plt.yscale("log") + plt.xlim(np.clip(k_s.min() - 2e-4, 1e-4, None), k_s.max()) + plt.ylim(1e1, 1e5) + plt.grid(which="both", axis="y") + + # Plot vertical lines for support points and binning + plt.vlines(k_s[:-1], ymin=1e1, ymax=1e5, colors="green", linestyles="dotted", linewidth=0.6) + plt.axvline( + k_s[-1], + color="green", + linestyle="dotted", + linewidth=0.6, + label=r"$\boldsymbol{\uptheta}$ support points", + ) + plt.vlines( + Pbins, + ymin=1e1, + ymax=5e2, + colors="red", + linestyles="dashed", + linewidth=0.5, + label=r"$\boldsymbol{\Phi}$ bin centres", + ) + plt.vlines( + Pbins_bnd, + ymin=1e1, + ymax=1e2 / 2, + colors="blue", + linestyles="dashed", + linewidth=0.5, + label=r"$\boldsymbol{\Phi}$ bin boundaries", + ) + + # Plot the Nyquist frequency + nyquist_freq = np.pi * size / L + plt.axvline( + nyquist_freq, ymax=1 / 6.0, color="orange", linestyle="-", linewidth=2, label="Nyquist" + ) + + # Add legend, optional title, and save the figure + plt.legend(loc="upper center", bbox_to_anchor=(0.5, -0.2), ncol=3) + if title: + plt.title(title) + output_dir = os.path.join(wd, "Figures") + os.makedirs(output_dir, exist_ok=True) + output_path = os.path.join(output_dir, "summary.pdf") + plt.savefig(output_path, bbox_inches="tight") + PrintInfo(f"Figure saved to: {output_path}") + + if display: + plt.show() + plt.close() + + +def relative_error_analysis( + G_sim, true_P, k_s, planck_Pk, Pbins, Pbins_bnd, size, L, wd, display=True +): + """ + Compute and plot the relative error between the interpolated and + true power spectra. + + Parameters + ---------- + G_sim : pysbmy.power.FourierGrid + Fourier grid object containing the `k_modes` attribute. + true_P : pysbmy.power.PowerSpectrum + Power spectrum object containing the `powerspectrum` attribute. + k_s : array-like + Support points in k-space. + planck_Pk : array-like + Power spectrum values at the support points. + Pbins : array-like + Centres of the Φ bins in k-space. + Pbins_bnd : array-like + Boundaries of the Φ bins in k-space. + size : float + Box size in number of grid cells. + L : float + Box length in Mpc/h. + wd : str + Working directory path for saving the figure. + display : bool, optional + Whether to display the figure. Default is True. + + Returns + ------- + None + """ + import os + from scipy.interpolate import InterpolatedUnivariateSpline + from selfisys.utils.logger import PrintInfo + + # Interpolate the power spectrum + spline = InterpolatedUnivariateSpline(k_s, planck_Pk, k=5) + rec_Pk = spline(G_sim.k_modes[1:]) + true_spectrum = true_P.powerspectrum[1:] + xx = G_sim.k_modes[1:] + + # Compute relative errors + rel_err = (rec_Pk - true_spectrum) / true_spectrum + indices_all = slice(None) + indices_nyquist = np.where((xx >= k_s.min()) & (xx <= np.pi * size / L))[0] + indices_k2e1 = np.where(xx <= 2e-1)[0] + + max_relerr = np.max(np.abs(rel_err[indices_all])) + max_relerr_nyquist = np.max(np.abs(rel_err[indices_nyquist])) + max_relerr_2e1 = np.max(np.abs(rel_err[indices_k2e1])) + + # Create the figure + plt.figure(figsize=(15, 5)) + plt.plot( + xx, + rel_err, + label=r"$\left(P_\textrm{interp}-P_{\mathrm{true}}\right)/P_{\mathrm{true}}$", + ) + plt.xlabel(r"$k\,[h/\mathrm{Mpc}]$") + plt.ylabel("Relative error") + plt.xscale("log") + plt.xlim(np.clip(k_s.min() - 2e-4, 1e-4, None), k_s.max()) + plt.ylim(-0.1, 0.1) + plt.grid(which="both", axis="y") + + # Vertical lines for binning and support points + plt.axvline( + x=Pbins[0], + color="red", + linestyle="dashed", + linewidth=0.5, + label=r"$\boldsymbol\Phi$ bin centres", + ) + plt.axvline(x=Pbins[-1], color="red", linestyle="dashed", linewidth=0.5) + for k in Pbins[1:-1]: + plt.axvline(x=k, ymax=1 / 6.0, color="red", linestyle="dashed", linewidth=0.5) + for k in k_s[:-1]: + plt.axvline(x=k, color="green", linestyle="dotted", linewidth=0.6) + plt.axvline( + x=k_s[-1], + color="green", + linestyle="dotted", + linewidth=0.6, + label=r"$\boldsymbol\uptheta$ support points", + ) + plt.axvline( + x=Pbins_bnd[0], + ymax=1 / 3.0, + color="blue", + linestyle="dashed", + linewidth=0.5, + label=r"$\boldsymbol\Phi$ bin boundaries", + ) + plt.axvline(x=Pbins_bnd[-1], ymax=1 / 3.0, color="blue", linestyle="dashed", linewidth=0.5) + for k in Pbins_bnd[1:-1]: + plt.axvline(x=k, ymax=1 / 12.0, color="blue", linestyle="dashed", linewidth=0.5) + + # Nyquist and fundamental frequencies + plt.axvline( + x=2 * np.pi / L, + ymax=1 / 6.0, + color="orange", + linestyle="-", + linewidth=2, + label="Fundamental mode", + ) + plt.axvline( + x=np.pi * size / L, + ymax=1 / 6.0, + color="orange", + linestyle="--", + linewidth=2, + label="Nyquist", + ) + + # Add title, legend, and save the figure + plt.legend(loc="upper center", bbox_to_anchor=(0.5, -0.2), ncol=3) + plt.title( + "Relative error between interpolated and true Planck 2018 power spectrum\n" + f"over the {G_sim.k_modes.size} modes of the Fourier grid (max: {max_relerr * 100:.3f}\\%)" + ) + output_dir = os.path.join(wd, "Figures") + os.makedirs(output_dir, exist_ok=True) + output_path = os.path.join(output_dir, "summary_relerr.pdf") + plt.savefig(output_path, bbox_inches="tight") + PrintInfo(f"Figure saved to: {output_path}") + + # Print summary of relative errors + PrintInfo(f"Max relative error over all support points: {max_relerr * 100:.3f}%") + PrintInfo(f"Max relative error up to 1D Nyquist frequency: {max_relerr_nyquist * 100:.3f}%") + PrintInfo(f"Max relative error up to k = 2e-1: {max_relerr_2e1 * 100:.3f}%") + + if display: + plt.show() + plt.close() + + +def plot_comoving_distance_redshift( + zz, cosmo, means_com, L, Lcorner, wd, colours_list=COLOUR_LIST, display=True +): + """ + Plot comoving distance as a function of redshift, highlighting key + scales. + + Parameters + ---------- + zz : array-like + Redshift range for the plot. + cosmo : astropy.cosmology object + Cosmology instance for calculating comoving distances. + means_com : array-like + Mean comoving distances of selection functions. + L : float + Box side length in Gpc/h. + Lcorner : float + Diagonal of the box (sqrt(3) * L) in Gpc/h. + wd : str + Working directory for saving figures. + colours_list : list + List of colours for selection function annotations. + display : bool, optional + Whether to display the figure. Default is True. + """ + d = cosmo.comoving_distance(zz) / 1e3 # Convert to Gpc/h + + plt.figure(figsize=(12, 5.2)) + plt.plot(zz, d, label="Comoving distance") + plt.axhline( + L, color="black", linewidth=1, linestyle="--", label=rf"$L = {L:.2f}\textrm{{ Gpc}}/h$" + ) + plt.axhline( + Lcorner, + color="orange", + linewidth=1, + linestyle="--", + label=rf"$L_\textrm{{corner}} = {Lcorner:.2f}\textrm{{ Gpc}}/h$", + ) + + # Annotate key redshifts + d_np = d.value + z_L = zz[np.argmin(np.abs(d_np - L))] + z_corner = zz[np.argmin(np.abs(d_np - Lcorner))] + plt.axvline(z_L, color="black", linewidth=0.5, alpha=0.5, linestyle="-") + plt.axvline(z_corner, color="orange", linewidth=0.5, alpha=0.5, linestyle="-") + plt.text(z_L, 1.07 * d_np.max(), rf"$z(L) = {z_L:.2f}$", fontsize=GLOBAL_FS_TINY - 2) + plt.text( + z_corner, + 1.07 * d_np.max(), + rf"$z(\sqrt{{3}}\,L) = {z_corner:.2f}$", + fontsize=GLOBAL_FS_TINY - 2, + ) + + # Annotate the selection functions' means + z_means = np.array([zz[np.argmin(np.abs(d_np - m))] for m in means_com]) + for i, z_mean in enumerate(z_means): + plt.axvline(z_mean, color=colours_list[i], linestyle="--", linewidth=1) + plt.text( + z_mean - 0.07, + L + 0.2, + rf"$z(\mu_{{{i+1}}} = {means_com[i]:.2f}) = {z_mean:.2f}$", + fontsize=GLOBAL_FS_TINY - 2, + rotation=90, + ) + + # Add labels, legend, and save the figure + plt.xlabel("Redshift $z$") + plt.ylabel(r"Comoving distance [Gpc$/h$]") + plt.grid(which="both", axis="both", linestyle="-", linewidth=0.3, color="gray", alpha=0.5) + plt.legend() + plt.tight_layout() + plt.savefig(f"{wd}selection_functions_z.pdf", bbox_inches="tight", dpi=300) + plt.savefig(f"{wd}selection_functions_z.png", bbox_inches="tight", dpi=300, transparent=True) + if display: + plt.show() + plt.close() + + +def redshift_distance_conversion( + zz, cosmo, means_com, L, Lcorner, xx, wd, colours_list=COLOUR_LIST, display=True +): + """ + Plot the conversion between comoving distance and redshift; return + the redshifts corresponding to the selection functions' means. + + Parameters + ---------- + zz : array-like + Redshift range for the plot. + cosmo : astropy.cosmology object + Cosmology instance for calculating comoving distances. + means_com : array-like + Mean comoving distances of selection functions. + L : float + Box side length in Gpc/h. + Lcorner : float + Diagonal of the box (sqrt(3) * L) in Gpc/h. + xx : array-like + Comoving distances at which to compute redshift. + wd : str + Working directory for saving figures. + colours_list : list + List of colours for selection function annotations. + display : bool, optional + Whether to display the figure. Default is True. + + Returns + ------- + spline : scipy.interpolate.UnivariateSpline + Linear interpolator to convert comoving distances to redshifts. + """ + from scipy.interpolate import UnivariateSpline + + # Convert comoving distances to redshifts using a linear interpolation + d_np = (cosmo.comoving_distance(zz) / 1e3).value # Gpc/h + spline = UnivariateSpline(d_np, zz, k=1, s=0) + z_x = spline(xx) + + plt.figure(figsize=(12, 5)) + plt.plot(xx, z_x) + + # Annotate key scales + plt.axvline( + L, color="black", linewidth=1, linestyle="--", label=rf"$L = {L:.2f}\textrm{{ Gpc}}/h$" + ) + plt.axhline(spline(L), color="black", linewidth=1, linestyle="--") + plt.axvline( + Lcorner, + color="orange", + linewidth=1, + linestyle="--", + label=rf"$L_\textrm{{corner}} = {Lcorner:.2f}\textrm{{ Gpc}}/h$", + ) + plt.axhline(spline(Lcorner), color="orange", linewidth=1, linestyle="--") + plt.text(L + 0.08, spline(L) - 0.14, rf"$z(L) = {spline(L):.2f}$", fontsize=GLOBAL_FS_TINY - 2) + plt.text( + Lcorner - 1.2, + spline(Lcorner) - 0.17, + rf"$z(\sqrt{{3}}\,L) = {spline(Lcorner):.2f}$", + fontsize=GLOBAL_FS_TINY - 2, + ) + + # Annotate the selection functions' means + z_means = spline(means_com) + for i, z_mean in enumerate(z_means): + plt.axvline(means_com[i], color=colours_list[i], linestyle="--", linewidth=1) + plt.axhline(z_mean, color=colours_list[i], linestyle="--", linewidth=1) + plt.text( + L + 0.08, + z_mean - 0.14, + rf"$z(\mu_{{{i+1}}} = {means_com[i]:.2f}) = {z_mean:.2f}$", + fontsize=GLOBAL_FS_TINY - 2, + ) + + # Add labels, legend, and save the figure + plt.xlabel(r"Comoving distance [Gpc$/h$]") + plt.ylabel("Redshift $z$") + plt.grid(which="both", axis="both", linestyle="-", linewidth=0.3, color="gray", alpha=0.5) + plt.legend() + plt.tight_layout() + plt.savefig(f"{wd}redshift_distance_conversion.pdf", bbox_inches="tight", dpi=300) + if display: + plt.show() + plt.close() + + return spline + + +def plot_selection_functions_def_in_z( + xx_of_zs, + res, + res_mis, + z_means, + cosmo, + L, + stds_z, + wd, + display=True, +): + """ + Plot radial lognormal (in redshift) selection functions against + comoving distances. + + Parameters + ---------- + xx_of_zs : array-like + Comoving distances mapped from redshift. + res : list of array-like + Selection functions for the well-specified model. + res_mis : list of array-like + Selection functions for the mis-specified model. + z_means : array-like + Mean redshifts of every galaxy population. + cosmo : object + Cosmology object. + L : float + Box side length in comoving distance units. + stds_z : array-like + Standard deviations of redshift distributions. + wd : str + Working directory for saving figures. + display : bool, optional + Whether to display the figure. Default is True. + + Returns + ------- + None + """ + from matplotlib.ticker import FormatStrFormatter + + colours_list = COLOUR_LIST[: len(res)] + + plt.figure(figsize=(10, 5)) + + # Plot well-specified selection functions + for i, r in enumerate(res): + plt.plot(xx_of_zs, r, color=colours_list[i]) + plt.plot(xx_of_zs, res[-1], color="black", alpha=0, label="Model A") + + # Plot mis-specified selection functions + for i, r_mis in enumerate(res_mis): + plt.plot(xx_of_zs, r_mis, linestyle="--", color=colours_list[i]) + plt.plot(xx_of_zs, res_mis[-1], linestyle="--", color="black", alpha=0, label="Model B") + + # Define x-ticks and labels + xticks = [0, np.sqrt(3) * L] + xtick_labels = [r"$0$", r"$\sqrt 3\,L \simeq {:.2f}$".format(np.sqrt(3) * L)] + plt.axvline(L, color="black", linestyle="-", linewidth=1, zorder=0) + + # Annotate populations + for i, mean in enumerate(z_means): + std = stds_z[i] + mu = np.log(mean**2 / np.sqrt(mean**2 + std**2)) + sig2 = np.log(1 + std**2 / mean**2) + mode = np.exp(mu - sig2) + + dmode = cosmo.comoving_distance(mode).value / 1e3 + dmean = cosmo.comoving_distance(mean).value / 1e3 + + xticks.extend([dmean]) + xtick_labels.extend([f"{dmean:.2f}"]) + plt.axvline(dmean, color=colours_list[i], linestyle="-.", linewidth=1) + plt.axvline(dmode, color=colours_list[i], linestyle="-", linewidth=1) + plt.axvline( + mode, + color=colours_list[i], + alpha=0, + linewidth=1, + label=f"Population {i+1}", + ) + + # Configure axes, labels, ticks, legend + plt.xlabel(r"$r\,[{\rm Gpc}/h]$", fontsize=GLOBAL_FS_LARGE) + plt.ylabel(r"$R_i(r)$", fontsize=GLOBAL_FS_LARGE) + plt.xticks(xticks, xtick_labels) + plt.tick_params(axis="x", which="major", size=8, labelsize=GLOBAL_FS_SMALL) + plt.tick_params(axis="y", which="major", size=8, labelsize=GLOBAL_FS_SMALL) + plt.grid(which="both", axis="both", linestyle="-", linewidth=0.4, color="gray", alpha=0.5) + + maxs = [np.max(r) for r in res] + yticks = [0] + maxs + plt.yticks(yticks) + plt.gca().yaxis.set_major_formatter(FormatStrFormatter("%.2f")) + + legend = plt.legend(frameon=True, loc="upper right", fontsize=GLOBAL_FS_LARGE) + legend.get_frame().set_edgecolor("white") + for lh in legend.legend_handles: + lh.set_alpha(1) + + plt.tight_layout() + plt.savefig(f"{wd}selection_functions_com.pdf", bbox_inches="tight", dpi=300) + if display: + plt.show() + plt.close() + + +def plot_galaxy_field_slice(g, size, L, wd, id_obs, limits="minmax", display=True): + """ + Plot a 2D slice of the observed field. + + Parameters + ---------- + g : ndarray + 2D array representing the observed field slice. + size : int + Number of grid points along each axis. + L : float + Size of the simulation box (in Mpc/h). + wd : str + Working directory for saving output files. + id_obs : int or str + Identifier for the observation, used in file naming. + limits : str, optional + Colormap scaling method. Options: 'minmax', 'truncate', 'max'. + display : bool, optional + Whether to display the figure. Default is True. + """ + from mpl_toolkits.axes_grid1 import make_axes_locatable + from matplotlib import colors + + # Define colormap and set scaling limits + GalaxyMap = create_colormap("GalaxyMap") + + if limits == "max": + maxcol = np.max(np.abs(g)) + mincol = -maxcol + cmap = GalaxyMap + elif limits == "truncate": + maxcol = np.min([np.max(-g), np.max(g)]) + mincol = -maxcol + cmap = "PiYG" + elif limits == "minmax": + maxcol = np.max(g) + mincol = np.min(g) + cmap = GalaxyMap + + divnorm = colors.TwoSlopeNorm(vmin=mincol, vcenter=0, vmax=maxcol) + + # Plot + fig, ax = plt.subplots(figsize=(6, 6)) + im = ax.imshow(g, norm=divnorm, cmap=cmap) + ax.invert_yaxis() # Place origin at bottom-left + ax.spines[["top", "right", "left", "bottom"]].set_visible(False) + divider = make_axes_locatable(ax) + cax = divider.append_axes("right", size="5%", pad=0.1) + cbar = fig.colorbar(im, cax=cax) + cbar.outline.set_visible(False) + ticks = [mincol, mincol / 2, 0, maxcol / 3, 2 * maxcol / 3, maxcol] + cbar.set_ticks(ticks) + cbar.set_ticklabels([f"{x:.2f}" for x in ticks], size=GLOBAL_FS_SMALL) + cbar.set_label(r"$\delta_\textrm{g}$", size=GLOBAL_FS) + ax.set_xticks( + [size * i / 4.0 for i in range(5)], [f"{L * 1e-3 * i / 4:.1f}" for i in range(5)] + ) + ax.set_yticks( + [size * i / 4.0 for i in range(5)], [f"{L * 1e-3 * i / 4:.1f}" for i in range(5)] + ) + ax.set_xlabel(r"Gpc/$h$", size=GLOBAL_FS) + ax.set_ylabel(r"Gpc/$h$", size=GLOBAL_FS) + + # Save or display + if display: + plt.show() + else: + plt.savefig(f"{wd}Figures/g_{id_obs}.png", bbox_inches="tight", dpi=300) + plt.savefig(f"{wd}Figures/g_{id_obs}.pdf", bbox_inches="tight", dpi=300) + plt.close() diff --git a/src/selfisys/utils/plot_params.py b/src/selfisys/utils/plot_params.py new file mode 100644 index 0000000..dc96735 --- /dev/null +++ b/src/selfisys/utils/plot_params.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Plotting utilities and custom colormaps for the SelfiSys package. + +This module provides custom Matplotlib settings, formatter classes, and +colormaps used for visualising results in the SelfiSys project. +""" + + +# Global font sizes +GLOBAL_FS = 20 +GLOBAL_FS_LARGE = 22 +GLOBAL_FS_XLARGE = 24 +GLOBAL_FS_SMALL = 18 +GLOBAL_FS_TINY = 16 +COLOUR_LIST = ["C4", "C5", "C6", "C7"] + + +def reset_plotting(): + import matplotlib as mpl + + mpl.rcParams.update(mpl.rcParamsDefault) + + +def setup_plotting(): + """ + Configure Matplotlib plotting settings for consistent appearance. + """ + import matplotlib.pyplot as plt + import importlib.resources + + with importlib.resources.open_text("selfisys", "preamble.tex") as f: + preamble = f.read() + + # Dictionary with rcParams settings + rcparams = { + "font.family": "serif", + "font.size": GLOBAL_FS, # Base font size + "axes.titlesize": GLOBAL_FS_XLARGE, + "axes.labelsize": GLOBAL_FS_LARGE, + "axes.linewidth": 1.0, + "xtick.labelsize": GLOBAL_FS_SMALL, + "ytick.labelsize": GLOBAL_FS_SMALL, + "xtick.major.width": 1.2, + "ytick.major.width": 1.2, + "xtick.minor.width": 1.0, + "ytick.minor.width": 1.0, + "xtick.direction": "in", + "ytick.direction": "in", + "xtick.major.pad": 5, + "xtick.minor.pad": 5, + "ytick.major.pad": 5, + "ytick.minor.pad": 5, + "legend.fontsize": GLOBAL_FS_SMALL, + "legend.title_fontsize": GLOBAL_FS_LARGE, + "figure.titlesize": GLOBAL_FS_XLARGE, + "figure.dpi": 300, + "grid.color": "gray", + "grid.linestyle": "dotted", + "grid.linewidth": 0.6, + "lines.linewidth": 2, + "lines.markersize": 8, + "text.usetex": True, + "text.latex.preamble": preamble, + } + + # Update rcParams + plt.rcParams.update(rcparams) + + +def dynamic_text_scaling(fig_height): + """ + Dynamically scale text sizes based on the vertical height of the + figure. + + Parameters + ---------- + fig_height : float + Height of the figure in inches. + + Returns + ------- + dict + Dictionary of scaled font sizes for consistent appearance. + """ + scaling_factor = fig_height / 6.0 # Reference height is 6 inches + return { + "font.size": GLOBAL_FS * scaling_factor, + "axes.titlesize": GLOBAL_FS_XLARGE * scaling_factor, + "axes.labelsize": GLOBAL_FS_LARGE * scaling_factor, + "xtick.labelsize": GLOBAL_FS_SMALL * scaling_factor, + "ytick.labelsize": GLOBAL_FS_SMALL * scaling_factor, + "legend.fontsize": GLOBAL_FS_SMALL * scaling_factor, + "legend.title_fontsize": GLOBAL_FS_LARGE * scaling_factor, + "figure.titlesize": GLOBAL_FS_XLARGE * scaling_factor, + } + + +class ScalarFormatterForceFormat_11: + """ + Custom scalar formatter to enforce a specific number format with an + offset. + + This formatter displays tick labels with one decimal place and + includes the offset notation for powers of ten. + """ + + def __init__(self, useOffset=True, useMathText=True, useLocale=None): + from matplotlib.ticker import ScalarFormatter + + self.formatter = ScalarFormatter( + useOffset=useOffset, useMathText=useMathText, useLocale=useLocale + ) + self.formatter.set_powerlimits((0, 0)) + + def __call__(self, val, pos=None): + return self.formatter.__call__(val, pos) + + def set_scientific(self, b): + self.formatter.set_scientific(b) + + def set_useOffset(self, b): + self.formatter.set_useOffset(b) + + def get_offset(self): + offset = self.formatter.get_offset() + if self.formatter.orderOfMagnitude != 0: + return r"$\times 10^{%+d}$" % self.formatter.orderOfMagnitude + else: + return r"$\times 10^{+0}$" + + +def get_contours(Z, nBins, confLevels=(0.3173, 0.0455, 0.0027)): + """ + Compute contour levels for given confidence levels. + + Parameters + ---------- + Z : ndarray + 2D histogram or density estimate. + nBins : int + Number of bins along one axis. + confLevels : tuple of float + Confidence levels for which to compute contour levels. + + Returns + ------- + chainLevels : ndarray + Contour levels corresponding to the provided confidence levels. + """ + import numpy as np + + Z = Z / Z.sum() + nContourLevels = len(confLevels) + chainLevels = np.ones(nContourLevels + 1) + histOrdered = np.sort(Z.flat) + histCumulative = np.cumsum(histOrdered) + nBinsFlat = np.linspace(0.0, nBins**2, nBins**2) + + for l in range(nContourLevels): + temp = np.interp(confLevels[l], histCumulative, nBinsFlat) + chainLevels[nContourLevels - 1 - l] = np.interp(temp, nBinsFlat, histOrdered) + + return chainLevels + + +def create_colormap(name): + """ + Create a custom colormap based on the specified name. + + Parameters + ---------- + name : str + The name of the colormap to create. + + Returns + ------- + ListedColormap + The requested custom colormap. + + Raises + ------ + ValueError + If the specified colormap name is not recognised. + """ + import numpy as np + from matplotlib import cm, colors, colormaps + + if name == "GalaxyMap": + # Colormap for slices through galaxy density fields + Ndots = 2**13 + stretch_top = 0.5 + truncate_bottom = 0.0 + stretch_bottom = 1.0 + + top = cm.get_cmap("RdPu", Ndots) + top = colors.LinearSegmentedColormap.from_list("", ["white", top(0.5), top(1.0)]) + bottom = cm.get_cmap("Greens_r", Ndots) + bottom = colors.LinearSegmentedColormap.from_list("", [bottom(0), bottom(0.5), "white"]) + + interp_top = np.linspace(0, 1, Ndots) ** stretch_top + interp_bottom = np.linspace(truncate_bottom, 1, Ndots) ** stretch_bottom + cols_galaxy = np.vstack((bottom(interp_bottom), top(interp_top))) + return colors.ListedColormap(cols_galaxy, name="GalaxyMap") + + elif name == "GradientMap": + # Colormap for gradient matrices + Ndots = 2**13 + stretch_bottom = 6.0 + stretch_top = 1 / 2.5 + truncate_bottom = 0.35 + + bottom = cm.get_cmap("BuGn_r", Ndots) + top = cm.get_cmap("RdPu", Ndots) + + interp_top = np.linspace(0, 1, Ndots) ** stretch_top + interp_bottom = np.linspace(truncate_bottom, 1, Ndots) ** stretch_bottom + newcolors = np.vstack((bottom(interp_bottom), top(interp_top))) + return colors.ListedColormap(newcolors, name="GradientMap") + + elif name == "CovarianceMap": + # Colormap for the diagonal blocks of covariance matrices + Ndots = 2**15 + stretch_top_1 = 0.3 + stretch_top_2 = 1.0 + stretch_bottom = 0.2 + middle = 0.4 # Middle of the positive scale, between 0 and 1 + cmap_name = "BrBG" + top = colormaps[cmap_name] + bottom = colormaps[cmap_name] + + interp_top = np.concatenate( + ( + middle * np.linspace(0.0, 1, Ndots // 2) ** stretch_top_1 + 0.5, + (1 - middle) * np.linspace(0.0, 1, Ndots // 2) ** stretch_top_2 + 0.5 + middle, + ) + ) + interp_bottom = np.linspace(0.0, 1.0, Ndots) ** stretch_bottom - 0.5 + newcolors = np.vstack((bottom(interp_bottom), top(interp_top))) + return colors.ListedColormap(newcolors, name="CovarianceMap") + + elif name == "FullCovarianceMap": + # Colormap for full covariance matrices + Ndots = 2**15 + stretch_top_1 = 0.3 + stretch_top_2 = 1.0 + middle_top = 0.4 # Middle of the positive scale, between 0 and 1 + stretch_bottom_1 = 1.0 + stretch_bottom_2 = 5.0 + middle_bottom = 0.7 # Middle of the negative scale, between 0 and 1 + colname = "PRGn_r" # Options: "PRGn", "PRGn_r", "BrBG", "PuOr" + top = colormaps[colname] + bottom = colormaps[colname] + + interp_top = np.concatenate( + ( + middle_top * np.linspace(0.0, 1, Ndots // 2) ** stretch_top_1 + 0.5, + (1 - middle_top) * np.linspace(0.0, 1, Ndots // 2) ** stretch_top_2 + + 0.5 + + middle_top, + ) + ) + interp_bottom = np.concatenate( + ( + middle_bottom * np.linspace(0.0, 1, Ndots // 2) ** stretch_bottom_1 - 0.5, + (1 - middle_bottom) * np.linspace(0.0, 1, Ndots // 2) ** stretch_bottom_2 + - 0.5 + + middle_bottom, + ) + ) + newcolors = np.vstack((bottom(interp_bottom), top(interp_top))) + return colors.ListedColormap(newcolors, name="FullCovarianceMap") + + elif name == "Blues_Reds": + # Additional colormap combining blues and reds + top = cm.get_cmap("Reds_r", 128) + bottom = cm.get_cmap("Blues", 128) + newcolors = np.vstack((top(np.linspace(0.7, 1, 128)), bottom(np.linspace(0, 1, 128)))) + return colors.ListedColormap(newcolors, name="Blues_Reds") + + elif name == "Purples_Oranges": + # Additional colormap combining purples and oranges + top = cm.get_cmap("Oranges_r", 128) + bottom = cm.get_cmap("Purples", 128) + newcolors = np.vstack((top(np.linspace(0.7, 1, 128)), bottom(np.linspace(0, 1, 128)))) + return colors.ListedColormap(newcolors, name="Purples_Oranges") + + else: + raise ValueError(f"Colormap '{name}' is not defined.") + + +def create_all_colormaps(): + """ + Create all custom colormaps. + + Returns + ------- + colormaps : dict + Dictionary containing all custom colormaps. + """ + colormaps_dict = {} + colormap_names = [ + "GalaxyMap", + "GradientMap", + "CovarianceMap", + "FullCovarianceMap", + "Blues_Reds", + "Purples_Oranges", + ] + for name in colormap_names: + colormaps_dict[name] = create_colormap(name) + return colormaps_dict diff --git a/src/selfisys/utils/plot_utils.py b/src/selfisys/utils/plot_utils.py new file mode 100644 index 0000000..baec3f4 --- /dev/null +++ b/src/selfisys/utils/plot_utils.py @@ -0,0 +1,2400 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +"""Plotting routines for the SelfiSys project. +""" + +import gc +from pathlib import Path +import numpy as np +import matplotlib.pyplot as plt +from matplotlib import gridspec, colors +from matplotlib.ticker import FormatStrFormatter, ScalarFormatter +from selfisys.utils.plot_params import * +from selfisys.utils.logger import getCustomLogger + +logger = getCustomLogger(__name__) + +# Configure global plotting settings +setup_plotting() + + +def plot_selection_functions( + x, + res, + res_mis, + params, + L, + corner, + axis="com", + zz=None, + zcorner=None, + z_L=None, + path=None, + force_plot=False, + labsAB=True, +): + """ + Plot selection functions. + + Parameters + ---------- + x : array-like + x-axis values (e.g., comoving distances or redshifts). + res : list of array-like + Selection functions for Model A. + res_mis : list of array-like, optional + Selection functions for Model B (optional). + params : tuple of (array-like, array-like, array-like), optional + Standard deviations, means, and normalisation factors for the + multiple galaxy populations. + L : float + Box size. + corner : float + Diagonal box size. + axis : str, optional + x-axis type ('com' for comoving distance, 'redshift' for + redshift). + zz : array-like, optional + Mapping between comoving distances and redshifts. + zcorner : float, optional + Redshift corresponding to the diagonal box size. + z_L : float, optional + Redshift corresponding to the box side length. + path : str, optional + Path to save the output plot. + force_plot : bool, optional + If True, displays the plot even if a path is specified. + labsAB : bool, optional + If True, labels models as 'Model A' and 'Model B'. + + Raises + ------ + RuntimeError + If unexpected errors occur during plotting. + """ + logger.info("Plotting selection functions...") + + try: + colours_list = COLOUR_LIST[: len(res)] + plt.figure(figsize=(10, 5)) + + # Plot rescaled selection functions for Model A + for i, r in enumerate(res): + plt.plot(x, r, color=colours_list[i]) # , label=None) + + # Plot rescaled selection functions for Model B, if provided + if res_mis is not None: + label_a = "Model A" if labsAB else None + plt.plot(x, res[-1], color="black", alpha=0, label=label_a) + for i, r_mis in enumerate(res_mis): + plt.plot(x, r_mis, linestyle="--", color=colours_list[i]) + label_b = "Model B" if labsAB else None + plt.plot(x, res_mis[-1], linestyle="--", color="black", alpha=0, label=label_b) + + # Configure x-axis ticks and labels + xticks = [0, corner] + xtick_labels = [r"$0$"] + if axis == "com" and corner is not None: + xtick_labels.append(r"$\sqrt 3\,L \simeq {:.2f}$".format(corner)) + elif axis == "redshift" and corner is not None: + xtick_labels.append(r"$z(\sqrt 3\,L) \simeq {:.3f}$".format(corner)) + if z_L is not None and L is not None: + xticks.insert(1, L) + xtick_labels.insert(1, r"$L=3.6$") + + # Annotate populations + for i, mean in enumerate(params[1]): + mean_plt = x[np.argmin(np.abs(zz - mean))] if zz is not None else mean + plt.axvline(mean_plt, color=colours_list[i], linestyle="--", linewidth=1.5) + lab_pop = f"Population {i + 1}" + plt.axvline(mean_plt, color=colours_list[i], alpha=0, linewidth=2, label=lab_pop) + xticks.append(mean_plt) + xtick_labels.append(r"${:.2f}$".format(mean_plt)) + + # Set axis labels + xlabel = r"$r\,[{\rm Gpc}/h]$" if axis == "com" else r"$z$" + ylabel = r"$R_i(r)$" if zcorner is None else r"$R_i$" + plt.xlabel(xlabel, fontsize=GLOBAL_FS_LARGE) + plt.ylabel(ylabel, fontsize=GLOBAL_FS_LARGE) + + # Configure ticks and grid + plt.xticks(xticks, xtick_labels) + plt.tick_params(axis="x", which="major", size=8, labelsize=GLOBAL_FS_SMALL) + plt.tick_params(axis="y", which="major", size=8, labelsize=GLOBAL_FS_SMALL) + plt.grid(which="both", axis="both", linestyle="-", linewidth=0.4, color="gray", alpha=0.5) + + # Add legend, save and display plot + fs_legend = GLOBAL_FS_LARGE + loc_legend = (0.6, 0.35) + legend = plt.legend(frameon=True, loc=loc_legend, fontsize=fs_legend) + for handle in legend.legend_handles: + handle.set_alpha(1) + + # Handle dual x-axes for redshift and comoving distance + if zcorner is not None: + ax2 = plt.gca().twiny() + ax2.set_xlabel(r"$z$", fontsize=GLOBAL_FS_LARGE) + zticks = ( + np.concatenate([[0], [z_L], [zcorner], params[1]]) + if z_L + else np.concatenate([[0], [zcorner], params[1]]) + ) + ax2.set_xticks(xticks) + ax2.set_xticklabels([r"${:.2f}$".format(z) for z in zticks]) + ax2.tick_params(axis="x", which="major", size=8, labelsize=GLOBAL_FS_SMALL) + ax2.grid( + which="both", axis="both", linestyle="-", linewidth=0.4, color="gray", alpha=0.5 + ) + + plt.tight_layout() + + if path: + plt.savefig(path, bbox_inches="tight", dpi=300) + plt.savefig(path.replace(".png", ".pdf"), bbox_inches="tight", dpi=300) + logger.info("Figure saved to %s", path) + if not path or force_plot: + plt.show() + plt.close() + + except Exception as e: + logger.critical("Unexpected error during plotting: %s", str(e)) + raise RuntimeError("Plotting failed.") from e + + +def plotly_3d(field, size=128, L=None, colormap="RdYlBu", limits="max"): + """ + Create an interactive 3D plot of volume slices using Plotly. + + Parameters + ---------- + field : array-like + 3D data field to visualise. + size : int, optional + Size of the field along one dimension. Default is 128. + L : float, optional + Physical size of the field in Mpc/h. Used for axis labels only. + colormap : str, optional + Colour map for visualisation. Default is 'RdYlBu'. + limits : str, optional + Colour scale limits ('max', 'truncate', or 'default'). Default + is 'max'. + + Returns + ------- + go.Figure + Plotly figure object. + """ + import plotly.graph_objects as go + + volume = field.T + rows, cols = volume[0].shape + + # Define colour scale limits + if limits == "max": + maxcol = np.max(np.abs(volume)) + mincol = -maxcol + elif limits == "truncate": + maxcol = min(np.max(-volume), np.max(volume)) + mincol = -maxcol + else: + maxcol = np.max(volume) + mincol = np.min(volume) + midcol = np.mean(volume) + + # Generate frames for the animation + nb_frames = size + frames = [ + go.Frame( + data=go.Surface( + z=(size - k) * np.ones((rows, cols)), + surfacecolor=np.flipud(volume[cols - 1 - k]), + cmin=mincol, + cmid=midcol, + cmax=maxcol, + ), + name=str(k), # Frames must be named for proper animation + ) + for k in range(nb_frames) + ] + + # Initial plot configuration + fig = go.Figure( + frames=frames, + data=go.Surface( + z=size * np.ones((rows, cols)), + surfacecolor=np.flipud(volume[cols // 2]), + colorscale=colormap, + cmin=mincol, + cmid=midcol, + cmax=maxcol, + colorbar=dict(thickness=20, ticklen=4), + ), + ) + + def frame_args(duration): + """Helper function to set animation frame arguments.""" + return { + "frame": {"duration": duration}, + "mode": "immediate", + "fromcurrent": True, + "transition": {"duration": duration, "easing": "linear"}, + } + + # Add animation slider + sliders = [ + { + "pad": {"b": 10, "t": 60}, + "len": 0.9, + "x": 0.1, + "y": 0, + "steps": [ + { + "args": [[f.name], frame_args(0)], + "label": str(k), + "method": "animate", + } + for k, f in enumerate(fig.frames) + ], + } + ] + + # Configure layout with or without physical size + layout_config = dict( + title="Slices in density field", + width=600, + height=600, + scene=dict( + zaxis=dict(range=[0, size - 1], autorange=False), + xaxis_title="x [Mpc/h]", + yaxis_title="y [Mpc/h]", + zaxis_title="z [Mpc/h]", + aspectratio=dict(x=1, y=1, z=1), + ), + updatemenus=[ + { + "buttons": [ + {"args": [None, frame_args(50)], "label": "▶", "method": "animate"}, + {"args": [[None], frame_args(0)], "label": "◼", "method": "animate"}, + ], + "direction": "left", + "pad": {"r": 10, "t": 70}, + "type": "buttons", + "x": 0.1, + "y": 0, + } + ], + sliders=sliders, + ) + if L is not None: + layout_config["scene"]["xaxis"] = dict( + ticktext=[0, L / 2, L], + tickvals=[0, size / 2, size], + title="x [Mpc/h]", + ) + layout_config["scene"]["yaxis"] = dict( + ticktext=[0, L / 2, L], + tickvals=[0, size / 2, size], + title="y [Mpc/h]", + ) + layout_config["scene"]["zaxis"]["ticktext"] = [0, L / 2, L] + layout_config["scene"]["zaxis"]["tickvals"] = [0, size / 2, size] + + fig.update_layout(**layout_config) + return fig + + +def plot_observations( + k_s, + theta_gt, + planck_Pk_EH, + P_0, + Pbins, + phi_obs, + Npop, + path=None, + force_plot=False, +): + """ + Plot the observed power spectra and related quantities. + + Parameters + ---------- + k_s : ndarray + Array of wavenumbers. + theta_gt : ndarray + Ground truth theta values. + planck_Pk_EH : ndarray + Planck power spectrum values. + P_0 : float + Normalisation constant for power spectra. + Pbins : ndarray + Vector of bin boundaries for the summary statistics. + phi_obs : ndarray + Observed summaries. + Npop : int + Number of populations. + path : str, optional + Path to save the output plot. + force_plot : bool, optional + If True, displays the plot even if a path is specified. + + Raises + ------ + RuntimeError + If unexpected errors occur during plotting. + + """ + logger.info("Plotting observations...") + + # Sanity checks + if len(k_s) == 0 or len(theta_gt) == 0 or len(planck_Pk_EH) == 0: + logger.warning("One or more input arrays are empty. The plot may be incomplete.") + + if len(k_s) != len(theta_gt) or len(k_s) != len(planck_Pk_EH): + logger.error("Mismatch in array lengths. Plotting may not reflect all data.") + + try: + _, ax1 = plt.subplots(figsize=(12, 5)) + + # Plot theta values + ax1.plot(k_s, theta_gt / P_0, label=r"$\boldsymbol{\uptheta}_{\mathrm{gt}}$", color="C0") + ax1.set_xscale("log") + ax1.semilogx( + k_s, + planck_Pk_EH / P_0, + label=r"$P_{\mathrm{Planck}}(k)/P_0(k)$", + color="C1", + lw=0.5, + ) + + ax1.set_xlabel(r"$k$ [$h$/Mpc]") + ax1.set_ylabel(r"$[{\mathrm{Mpc}}/h]^3$") + ax1.grid(which="both", axis="y", linestyle="dotted", linewidth=0.6) + + # Vertical lines for theta support wavenumbers + for k in k_s[:-1]: + ax1.axvline(x=k, color="green", linestyle="dotted", linewidth=0.6) + ax1.axvline( + x=k_s[-1], + color="green", + linestyle="dotted", + linewidth=0.6, + label=r"$\boldsymbol{\uptheta}$ support wavenumbers", + ) + + # Vertical lines for Phi bin centres + ax2 = ax1.twinx() + ax1.axvline(x=Pbins[0], color="red", linestyle="dashed", linewidth=0.5) + ax2.axvline( + x=Pbins[-1], + color="red", + linestyle="dashed", + linewidth=0.5, + label=r"$\boldsymbol{\Phi}$-bins centres", + ) + for k in Pbins[1:-1]: + ax1.axvline(x=k, ymax=0.167, color="red", linestyle="dashed", linewidth=0.5) + + ax1.legend(loc="lower left", fontsize=GLOBAL_FS) + ax1.set_xlim(max(1e-4, k_s.min() - 2e-4), k_s.max()) + ax1.set_ylim(7e-1, 1.6e0) + + # Plot observations + len_obs = len(phi_obs) // Npop + + if len(phi_obs) % Npop != 0: + logger.warning( + "Length of 'phi_obs' is not divisible by the number of populations. " + "Ensure input dimensions are consistent.", + ) + + for i in range(Npop): + ax2.plot( + Pbins, + phi_obs[i * len_obs : (i + 1) * len_obs], + marker="x", + label=r"$\boldsymbol{\Phi}_{\mathrm{obs}}$, population " + str(i), + linewidth=0.5, + color=COLOUR_LIST[i], + ) + + ax2.legend(loc="upper right", fontsize=GLOBAL_FS) + + if path: + plt.savefig(path, bbox_inches="tight", dpi=300) + plt.savefig(path.replace(".png", ".pdf"), bbox_inches="tight", dpi=300) + logger.info("Figure saved to %s", path) + if not path or force_plot: + plt.show() + plt.close() + + except Exception as e: + logger.critical("Unexpected error during plotting: %s", str(e)) + raise RuntimeError("Plotting failed.") from e + + +def plot_reconstruction( + k_s, + Pbins, + prior_theta_mean, + prior_theta_covariance, + posterior_theta_mean, + posterior_theta_covariance, + theta_gt, + P_0, + phi_obs=None, + suptitle=None, + theta_fid=None, + savepath=None, + force_plot=False, + legend_loc="upper right", + enforce_ylims=True, +): + """ + Plot the prior, posterior and ground truth power spectra. + + Parameters + ---------- + k_s : ndarray + Array of wavenumbers. + Pbins : ndarray + Vector of bin boundaries for the summary statistics. + prior_theta_mean : ndarray + Mean of the prior distribution. + prior_theta_covariance : ndarray + Covariance of the prior distribution. + posterior_theta_mean : ndarray + Mean of the posterior distribution. + posterior_theta_covariance : ndarray + Covariance of the posterior distribution. + theta_gt : ndarray + Ground truth power spectrum. + P_0 : float + Normalisation constant for the power spectrum. + phi_obs : ndarray, optional + Observed summaries. + suptitle : str, optional + Plot title. Leave empty for no title. + theta_fid : ndarray, optional + Fiducial theta for hyperparameter tuning (for some priors). + savepath : str or Path, optional + Path to save the plot. + force_plot : bool, optional + If True, displays the plot even if a path is specified. + legend_loc : str, optional + Location of the plot legend. + enforce_ylims : bool, optional + Enforce y-axis limits. + + Raises + ------ + RuntimeError + If unexpected errors occur during plotting. + """ + logger.info("Generating power spectrum reconstruction plot.") + + try: + fig, ax = plt.subplots(figsize=(14, 5)) + + # Prior + ax.plot( + k_s, + prior_theta_mean, + linestyle="-", + color="gold", + label="$\\boldsymbol{\\uptheta}_0$ (prior)", + ) + ax.fill_between( + k_s, + prior_theta_mean - 2 * np.sqrt(np.diag(prior_theta_covariance)), + prior_theta_mean + 2 * np.sqrt(np.diag(prior_theta_covariance)), + color="gold", + alpha=0.2, + ) + + # Fiducial theta used for hyperparameter tuning with some priors + if theta_fid is not None: + ax.plot( + k_s, + theta_fid, + linestyle="--", + color="C1", + label="$\\boldsymbol{\\uptheta}_{\\mathrm{fid}}$ (fiducial)", + ) + + # Posterior + ax.plot( + k_s, + posterior_theta_mean, + color="C2", + label="$\\boldsymbol{\\upgamma}$ (reconstruction)", + ) + ax.fill_between( + k_s, + posterior_theta_mean - 2 * np.sqrt(np.diag(posterior_theta_covariance)), + posterior_theta_mean + 2 * np.sqrt(np.diag(posterior_theta_covariance)), + color="C2", + alpha=0.35, + ) + + # Ground truth + ax.plot( + k_s, + theta_gt / P_0, + color="C0", + label="$\\boldsymbol{\\uptheta}_\\mathrm{gt}$ (groundtruth)", + ) + + # Plot the binning + ymin, ymax = ax.get_ylim() + for i in range(len(k_s)): + ax.axvline( + k_s[i], + ymin=ymin, + ymax=ymax, + linestyle=":", + linewidth=0.8, + color="green", + alpha=0.5, + ) + ax.vlines( + Pbins, ymin=ymin, ymax=0.8, linestyle="--", linewidth=0.8, color="red", alpha=0.5 + ) + + # Overlay observations if provided + if phi_obs is not None: + ax2 = ax.twinx() + ax2.plot(Pbins, phi_obs, "C3.-", label=r"$\Phi_{O}$ (observations)") + ax2.legend(loc="lower right", fontsize=GLOBAL_FS_LARGE) + + ax.set_xlim([k_s.min() - 0.0001, k_s.max()]) + ax.set_xscale("log") + ax.grid(visible=True, which="both", linestyle=":", color="grey") + ax.xaxis.set_tick_params(which="both", direction="in", width=1.0) + ax.xaxis.set_tick_params(which="major", length=6, labelsize=GLOBAL_FS) + ax.xaxis.set_tick_params(which="minor", length=4) + ax.yaxis.set_tick_params(which="both", direction="in", width=1.0) + ax.yaxis.set_tick_params(which="major", length=6, labelsize=GLOBAL_FS) + if enforce_ylims: + ax.set_ylim([0.85, 1.35]) + ax.set_xlabel("$k$ [$h$/Mpc]", size=GLOBAL_FS_LARGE) + ax.set_ylabel("$\\theta(k) = P(k)/P_0(k)$", size=GLOBAL_FS_LARGE) + ax.legend(loc=legend_loc, fontsize=GLOBAL_FS_LARGE) + + plt.suptitle(suptitle, fontsize=GLOBAL_FS_XLARGE) if suptitle else None + + # Save / display + if savepath: + fig.savefig(savepath, bbox_inches="tight", dpi=300, format="png", transparent=True) + fig.savefig( + savepath[:-4] + "_white.png", + bbox_inches="tight", + dpi=300, + format="png", + transparent=False, + ) + fig.savefig(savepath[:-4] + ".pdf", bbox_inches="tight", dpi=300, format="pdf") + fig.suptitle("") + fig.savefig( + savepath[:-4] + "_notitle.png", + bbox_inches="tight", + dpi=300, + format="png", + transparent=True, + ) + fig.savefig( + savepath[:-4] + "_white_notitle.png", + bbox_inches="tight", + dpi=300, + format="png", + transparent=False, + ) + fig.savefig( + savepath[:-4] + "_notitle.pdf", + bbox_inches="tight", + dpi=300, + format="pdf", + ) + + logger.info("Figure saved to %s", savepath) + + if force_plot or savepath is None: + plt.show() + + except Exception as e: + logger.critical("Unexpected error during plotting: %s", str(e)) + raise RuntimeError("Plotting failed.") from e + finally: + plt.close(fig) + del fig, ax + gc.collect() + + +def plot_fisher(F0, params_names_fisher, title=None, path=None): + """ + Plot the Fisher matrix as a heatmap. + + Parameters + ---------- + F0 : ndarray + Fisher matrix. + params_names_fisher : list of str + Names for the axes. + title : str, optional + Title of the plot. Default is "Fisher matrix". + path : str or Path, optional + Path to save the plot. If None, the plot is displayed. + + Raises + ------ + RuntimeError + If unexpected errors occur during plotting. + """ + import seaborn as sns + + logger.info("Generating Fisher matrix plot.") + try: + plt.figure(figsize=(10, 10)) + + # Normalisation for the colourmap + F0min, F0max = F0.min(), F0.max() + center = 0 if F0min < 0 and F0max > 0 else np.mean(F0) + divnorm = colors.TwoSlopeNorm(vmin=F0min, vcenter=center, vmax=F0max) + + # Plot the Fisher matrix + sns.heatmap( + F0, + annot=True, + fmt=".2e", + cmap="RdBu_r", + norm=divnorm, + square=True, + cbar_kws={"shrink": 0.8}, + ) + + plt.xticks(np.arange(len(params_names_fisher)) + 0.5, params_names_fisher, rotation=0) + plt.yticks(np.arange(len(params_names_fisher)) + 0.5, params_names_fisher, rotation=0) + plt.title(title or "Fisher matrix") + + if path: + path = Path(path) + for fmt in ["png", "pdf"]: + plt.savefig( + path.with_suffix(f".{fmt}"), + bbox_inches="tight", + dpi=300, + format=fmt, + transparent=True, + ) + + logger.info("Figure saved to %s", path) + else: + logger.info("Displaying plot.") + plt.show() + + except Exception as e: + logger.critical("Unexpected error during Fisher matrix plotting: %s", str(e)) + raise RuntimeError("Fisher matrix plotting failed.") from e + finally: + plt.close() + gc.collect() + + +def plot_mocks_compact( + NORM, + N, + P, + Pbins, + phi_obs, + Phi_0, + f_0, + C_0, + suptitle=None, + force_plot=False, + savepath=None, +): + """ + Plot and compare observed and simulated power spectra. + + Parameters + ---------- + NORM : float + Normalisation factor for the observed spectra. + N : int + Number of mock data realisations. + P : int + Number of bins for the summaries. + Pbins : ndarray + Vector of bin boundaries for the summary statistics. + phi_obs : ndarray + Observed power spectrum. + Phi_0 : ndarray + Mock realisations of the power spectrum. + f_0 : ndarray + Mean power spectrum. + C_0 : ndarray + Covariance matrix of the mock summaries. + suptitle : str, optional + Title for the plot. + force_plot : bool, optional + If True, displays the plot even if a path is specified. + savepath : str or Path, optional + Path to save the plot. + + Raises + ------ + RuntimeError + If an unexpected error occurs during plotting. + """ + logger.info("Plotting mock power spectra (compact)...") + + alpha_mocks = 0.25 + alpha_binning = 0.3 + + try: + Phi_0_full = Phi_0.copy() + phi_obs_full = phi_obs.copy() + f_0_full = f_0.copy() + C_0_full = C_0.copy() + + idx = 0 + Phi_0 = Phi_0[:, idx * P : (idx + 1) * P] + phi_obs = phi_obs[idx * P : (idx + 1) * P] + f_0 = f_0[idx * P : (idx + 1) * P] + C_0 = C_0[idx * P : (idx + 1) * P, idx * P : (idx + 1) * P] + + COLOUR_LIST_means = ["darkorchid", "saddlebrown", "mediumvioletred"] + + fig = plt.figure(figsize=(10, 10)) + gs0 = gridspec.GridSpec( + 3, + 2, + width_ratios=[1.0, 1.0], + height_ratios=[1.0, 1.0, 1.0], + wspace=0.0, + hspace=0.0, + ) + gs0.update(right=1.0, left=0.0) + ax0 = plt.subplot(gs0[0, 0]) + ax0b = plt.subplot(gs0[0, 1]) + ax01 = plt.subplot(gs0[1, 0], sharex=ax0) + ax01b = plt.subplot(gs0[1, 1]) + ax02 = plt.subplot(gs0[2, 0], sharex=ax0) + ax02b = plt.subplot(gs0[2, 1]) + axx0x = [[ax01, ax01b], [ax02, ax02b]] + + # Observed power spectrum (normalised) + ax0.semilogx( + Pbins, + phi_obs * NORM, + linewidth=2, + color="black", + label=r"$\boldsymbol{\Phi}_\mathrm{O}$", + zorder=3, + ) + + # Realisations at the expansion point + for i in range(N - 1): + ax0.semilogx(Pbins, Phi_0[i], color="C7", alpha=alpha_mocks, linewidth=0.7) + + # Average value + ax0.semilogx( + Pbins, + Phi_0[N - 1], + color="C7", + alpha=alpha_mocks, + linewidth=0.7, + ) + ax0.semilogx( + Pbins, + f_0, + linewidth=2, + color=COLOUR_LIST_means[idx], + linestyle="--", + label=r"$\textbf{f}_0$", + zorder=2, + ) + + # 2-sigma intervals + ax0.fill_between( + Pbins, + f_0 - 2 * np.sqrt(np.diag(C_0)), + f_0 + 2 * np.sqrt(np.diag(C_0)), + color=COLOUR_LIST[idx], + alpha=0.4, + label=r"2 $\sqrt{\mathrm{diag}(\textbf{C}_0)}$", + zorder=2, + ) + + # Plot the binning + (ymin, ymax) = ax0.get_ylim() + ax0.set_ylim([ymin, ymax]) + for i in range(len(Pbins)): + ax0.plot( + (Pbins[i], Pbins[i]), + (ymin, ymax), + linestyle="--", + linewidth=0.8, + color="red", + alpha=alpha_binning, + zorder=1, + ) + + ax0.set_xlim([Pbins.min() - 0.0001, Pbins.max() + 0.01]) + ax0.set_ylabel("population 1", size=GLOBAL_FS) + ax0.legend(fontsize=GLOBAL_FS, loc="upper right") + ax0.xaxis.set_ticks_position("both") + ax0.xaxis.set_tick_params(which="both", direction="in", width=1.0) + for axis in ["top", "bottom", "left", "right"]: + ax0.spines[axis].set_linewidth(1.0) + ax0.xaxis.set_tick_params(which="major", length=6) + ax0.xaxis.set_tick_params(which="minor", length=4) + if suptitle is not None: + ax0.set_title( + r"$\boldsymbol{\Phi}$ (" + suptitle + ")", y=1.05, fontsize=GLOBAL_FS_LARGE + ) + else: + ax0.set_title(r"$\boldsymbol{\Phi}$", y=1.05, fontsize=GLOBAL_FS_LARGE) + ax0.tick_params(axis="y", which="major", labelsize=GLOBAL_FS_TINY) + ax0.yaxis.set_major_formatter(FormatStrFormatter("%.1f")) + + # Same as above but normalise everything by the observations: + normalisation = phi_obs + + ax0b.set_xlim([Pbins.min() - 0.0001, Pbins.max() + 0.01]) + + # Observed power spectrum (normalised) + ax0b.semilogx( + Pbins, + NORM * phi_obs / normalisation, + linewidth=2, + color="black", + label=r"$\boldsymbol{\Phi}_\mathrm{O}$", + zorder=3, + ) + + for i in range(N - 1): + ax0b.semilogx( + Pbins, + Phi_0[i] / normalisation, + color="C7", + alpha=alpha_mocks, + linewidth=0.7, + ) + + ax0b.semilogx( + Pbins, + Phi_0[N - 1] / normalisation, + color="C7", + alpha=alpha_mocks, + linewidth=0.7, + label=r"$\boldsymbol{\Phi}_{\theta_0}$", + ) + + ax0b.semilogx( + Pbins, + f_0 / normalisation, + linewidth=2, + color=COLOUR_LIST_means[idx], + linestyle="--", + label=r"$\textbf{f}_0$", + zorder=2, + ) + + # 2-sigma intervals + ax0b.fill_between( + Pbins, + f_0 / normalisation - 2 * np.sqrt(np.diag(C_0)) / normalisation, + f_0 / normalisation + 2 * np.sqrt(np.diag(C_0)) / normalisation, + color=COLOUR_LIST[idx], + alpha=0.4, + label=r"2 $\sqrt{\mathrm{diag}(\textbf{C}_0)}$", + zorder=2, + ) + + # Plot the binning + (ymin, ymax) = ax0b.get_ylim() + ax0b.set_ylim([ymin, ymax]) + for i in range(len(Pbins)): + ax0b.plot( + (Pbins[i], Pbins[i]), + (ymin, ymax), + linestyle="--", + linewidth=0.8, + color="red", + alpha=alpha_binning, + zorder=1, + ) + + ax0b.set_xlabel(r"$k$ [$h$/Mpc]", size=GLOBAL_FS) + ax0b.xaxis.set_ticks_position("both") + ax0b.xaxis.set_tick_params(which="both", direction="in", width=1.0) + for axis in ["top", "bottom", "left", "right"]: + ax0b.spines[axis].set_linewidth(1.0) + ax0b.xaxis.set_tick_params(which="major", length=6) + ax0b.xaxis.set_tick_params(which="minor", length=4) + ax0b.tick_params(axis="y", which="major", labelsize=GLOBAL_FS_TINY) + ax0b.yaxis.tick_right() + if suptitle is not None: + ax0b.set_title( + r"$\boldsymbol{\Phi}/\boldsymbol{\Phi}_\mathrm{O}$ (" + suptitle + ")", + y=1.05, + fontsize=GLOBAL_FS, + ) + else: + ax0b.set_title( + r"$\boldsymbol{\Phi}/\boldsymbol{\Phi}_\mathrm{O}$", y=1.05, fontsize=GLOBAL_FS + ) + + for ax, axb in axx0x: + idx += 1 + Phi_0 = Phi_0_full[:, idx * P : (idx + 1) * P] + phi_obs = phi_obs_full[idx * P : (idx + 1) * P] + f_0 = f_0_full[idx * P : (idx + 1) * P] + C_0 = C_0_full[idx * P : (idx + 1) * P, idx * P : (idx + 1) * P] + + # Same plot but for the other axes: + ax.set_xlim([Pbins.min() - 0.0001, Pbins.max() + 0.01]) + + # Observed power spectrum (normalised) + ax.semilogx(Pbins, phi_obs, linewidth=2, color="black", zorder=3) + for i in range(N - 1): + ax.semilogx(Pbins, Phi_0[i], color="C7", alpha=alpha_mocks, linewidth=0.7) + ax.semilogx(Pbins, Phi_0[N - 1], color="C7", alpha=alpha_mocks, linewidth=0.7) + ax.semilogx( + Pbins, + f_0, + linewidth=2, + color=COLOUR_LIST_means[idx], + linestyle="--", + label=r"$\textbf{f}_0$", + zorder=2, + ) + + # 2-sigma intervals + ax.fill_between( + Pbins, + f_0 - 2 * np.sqrt(np.diag(C_0)), + f_0 + 2 * np.sqrt(np.diag(C_0)), + color=COLOUR_LIST[idx], + alpha=0.4, + label=r"2 $\sqrt{\mathrm{diag}(\textbf{C}_0)}$", + zorder=2, + ) + + # Plot the binning: + (ymin, ymax) = ax.get_ylim() + ax.set_ylim([ymin, ymax]) + for i in range(len(Pbins)): + ax.plot( + (Pbins[i], Pbins[i]), + (ymin, ymax), + linestyle="--", + linewidth=0.8, + color="red", + alpha=alpha_binning, + zorder=1, + ) + ax.set_ylabel("population " + str(idx + 1), size=GLOBAL_FS) + ax.set_xlabel(r"$k$ [$h$/Mpc]", size=GLOBAL_FS) + ax.tick_params(axis="x", which="major", labelsize=GLOBAL_FS_SMALL, pad=8) + ax.xaxis.set_ticks_position("both") + ax.xaxis.set_tick_params(which="both", direction="in", width=1.0) + ax.tick_params(axis="y", which="major", labelsize=GLOBAL_FS_TINY) + ax.yaxis.set_major_formatter(FormatStrFormatter("%.1f")) + for axis in ["top", "bottom", "left", "right"]: + ax.spines[axis].set_linewidth(1.0) + ax.xaxis.set_tick_params(which="major", length=6) + ax.xaxis.set_tick_params(which="minor", length=4) + ax.legend(loc="upper right", fontsize=GLOBAL_FS) + + # Normalise everything by the observations: + normalisation = phi_obs + + axb.set_xlim([Pbins.min() - 0.0001, Pbins.max() + 0.01]) + + # Observed power spectrum (normalised) + axb.semilogx( + Pbins, + phi_obs / normalisation, + linewidth=2, + color="black", + label=r"$\boldsymbol{\Phi}_\mathrm{O}$", + zorder=3, + ) + + for i in range(N - 1): + axb.semilogx( + Pbins, + Phi_0[i] / normalisation, + color="C7", + alpha=alpha_mocks, + linewidth=0.7, + ) + + axb.semilogx( + Pbins, + Phi_0[N - 1] / normalisation, + color="C7", + alpha=alpha_mocks, + linewidth=0.7, + label=r"$\boldsymbol{\Phi}_{\theta_0}$", + ) + + axb.semilogx( + Pbins, + f_0 / normalisation, + linewidth=2, + color=COLOUR_LIST_means[idx], + linestyle="--", + label=r"$\textbf{f}_0$", + zorder=2, + ) + + # 2-sigma intervals + axb.fill_between( + Pbins, + f_0 / normalisation - 2 * np.sqrt(np.diag(C_0)) / normalisation, + f_0 / normalisation + 2 * np.sqrt(np.diag(C_0)) / normalisation, + color=COLOUR_LIST[idx], + alpha=0.4, + label=r"2 $\sqrt{\mathrm{diag}(\textbf{C}_0)}$", + zorder=2, + ) + + # Plot the binning + (ymin, ymax) = axb.get_ylim() + axb.set_ylim([ymin, ymax]) + for i in range(len(Pbins)): + axb.plot( + (Pbins[i], Pbins[i]), + (ymin, ymax), + linestyle="--", + linewidth=0.8, + color="red", + alpha=alpha_binning, + zorder=1, + ) + + axb.set_xlabel(r"$k$ [$h$/Mpc]", size=GLOBAL_FS) + axb.tick_params(axis="x", which="major", labelsize=GLOBAL_FS_SMALL, pad=8) + axb.xaxis.set_ticks_position("both") + axb.xaxis.set_tick_params(which="both", direction="in", width=1.0) + axb.tick_params(axis="y", which="major", labelsize=GLOBAL_FS_TINY) + axb.yaxis.tick_right() + for axis in ["top", "bottom", "left", "right"]: + axb.spines[axis].set_linewidth(1.0) + axb.xaxis.set_tick_params(which="major", length=6) + axb.xaxis.set_tick_params(which="minor", length=4) + + if savepath: + savepath = Path(savepath) + for fmt in ["png", "pdf"]: + fig.savefig(savepath.with_suffix(f".{fmt}"), bbox_inches="tight", dpi=300) + logger.info("Plot saved to %s", savepath) + if force_plot or not savepath: + plt.show() + + except Exception as e: + logger.critical("Unexpected error during plotting: %s", str(e)) + raise RuntimeError("Plotting failed.") from e + finally: + plt.close(fig) + del fig, ax0, ax0b, ax01, ax01b, ax02, ax02b + gc.collect() + + +def plot_C( + C_0, + X, + Y, + Pbins, + CMap, + binning=True, + suptitle=None, + savepath=None, + force=False, +): + """ + Plot covariance matrix. + + Parameters + ---------- + C_0 : ndarray + Covariance matrix. + X : ndarray + X-axis grid for plotting. + Y : ndarray + Y-axis grid for plotting. + Pbins : ndarray + Vector of bin boundaries for the summary statistics. + CMap : str + Colormap for the plot. + binning : bool, optional + Whether to overlay bin lines on the plot. Default is True. + suptitle : str, optional + Title for the plot. If None, a default title is used. + savepath : str or Path, optional + Path to save the plot. If None, the plot is displayed. + force : bool, optional + If True, displays the plot even if savepath is specified. + + Raises + ------ + RuntimeError + If unexpected errors occur during plotting. + """ + + from itertools import product + + logger.info("Plotting covariance matrix...") + try: + fig, axs = plt.subplots(3, 3, figsize=(13, 11)) + P = len(Pbins) + + # Determine vmin, vmax and central value for TwoSlopeNorm + vmin, vmax = C_0.min(), C_0.max() + centerval = 0 if vmin < 0 and vmax > 0 else np.mean(C_0) + divnorm = colors.TwoSlopeNorm(vmin=vmin, vcenter=centerval, vmax=vmax) + + for i, j in product(range(3), range(3)): + C_0_ij = C_0[i * P : (i + 1) * P, j * P : (j + 1) * P] + imat = 2 - i # Invert to place origin at bottom left + ax = axs[imat, j] + ax.set_aspect("equal") + ax.set_xscale("log") + ax.set_yscale("log") + ax.xaxis.set_ticks_position("both") + ax.yaxis.set_ticks_position("both") + + # Plot the covariance matrix + im1 = ax.pcolormesh(X, Y, C_0_ij[:-1, :-1], shading="flat", norm=divnorm, cmap=CMap) + + # Overlay the binning grid (if enabled) + if binning: + for n in range(len(Pbins)): + ax.plot( + (Pbins[n], Pbins[n]), + (Pbins.min(), Pbins.max()), + linestyle="--", + linewidth=0.5, + color="red", + alpha=0.5, + ) + ax.plot( + (Pbins.min(), Pbins.max()), + (Pbins[n], Pbins[n]), + linestyle="--", + linewidth=0.5, + color="red", + alpha=0.5, + ) + + # Custom ticks for boundary axes + if i == 0: + ax.xaxis.set_tick_params( + which="both", direction="in", width=1.0, labelsize=GLOBAL_FS + ) + ax.xaxis.set_tick_params(which="major", length=6) + ax.xaxis.set_tick_params(which="minor", length=4) + else: + ax.set_xticks([]) + + if j == 0: + ax.yaxis.set_tick_params( + which="both", direction="in", width=1.0, labelsize=GLOBAL_FS + ) + ax.yaxis.set_tick_params(which="major", length=6) + ax.yaxis.set_tick_params(which="minor", length=4) + else: + ax.set_yticks([]) + + # Set title and adjust layout + suptitle = r"$\textbf{C}_0$" if suptitle is None else r"$\textbf{C}_0$ (" + suptitle + ")" + plt.suptitle(suptitle, y=0.94, x=0.45, size=GLOBAL_FS + GLOBAL_FS_XLARGE) + plt.subplots_adjust(wspace=0, hspace=0) + + # Colourbar + cbar = fig.colorbar( + im1, ax=axs.ravel().tolist(), shrink=1, pad=0.009, aspect=40, orientation="vertical" + ) + cbar.ax.tick_params( + axis="y", direction="in", width=1.0, length=6, labelsize=GLOBAL_FS_LARGE + ) + cbar.update_normal(im1) + cbar.mappable.set_clim(vmin=C_0[:-1, :-1].min(), vmax=C_0[:-1, :-1].max()) + + loc_xticks = np.concatenate([np.linspace(vmin, 0, 5), np.linspace(0, vmax, 5)[1:]]) + val_xticks = np.round(loc_xticks, 2) + cbar.set_ticks(loc_xticks, labels=val_xticks) + + # Axis labels + fig.text(0.45, 0.04, r"$k$ [$h$/Mpc]", ha="center", size=GLOBAL_FS_XLARGE) + fig.text( + 0.04, 0.5, r"$k'$ [$h$/Mpc]", va="center", rotation="vertical", size=GLOBAL_FS_XLARGE + ) + + # Save / display + if savepath: + savepath = Path(savepath) + savepath.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(savepath, bbox_inches="tight", dpi=300, format="png", transparent=True) + fig.savefig(savepath.with_suffix(".pdf"), bbox_inches="tight", dpi=300) + + logger.info("Plot saved to %s", savepath) + + if force or not savepath: + plt.show() + + except Exception as e: + logger.critical("Unexpected error during plotting: %s", str(e)) + raise RuntimeError("Plotting failed.") from e + finally: + plt.close(fig) + del fig, axs, im1 + gc.collect() + + +def plot_prior_and_posterior_covariances( + X, + Y, + k_s, + prior_theta_covariance, + prior_covariance, + posterior_theta_covariance, + P_0, + force_plot=False, + suptitle="", + savepath=None, +): + """ + Plot prior and posterior covariance matrices. + + Parameters + ---------- + X : ndarray + X-axis grid. + Y : ndarray + Y-axis grid. + k_s : ndarray + Wavenumbers. + prior_theta_covariance : ndarray + Prior covariance matrix for normalised spectra. + prior_covariance : ndarray + Prior covariance matrix for unnormalised spectra. + posterior_theta_covariance : ndarray + Posterior covariance matrix for normalised spectra. + P_0 : ndarray + Fiducial power spectrum used for normalisation. + force_plot : bool, optional + Display plot even if savepath is set. + suptitle : str, optional + Title for the plot. + savepath : str or Path, optional + Path to save the plot. If None, the plot is displayed. + verbose : int, optional + Verbosity level (0=silent, 1=default, 2=detailed). + + Raises + ------ + RuntimeError + If unexpected errors occur during plotting. + """ + from mpl_toolkits.axes_grid1 import make_axes_locatable + + logger.info("Plotting prior and posterior covariance matrices...") + try: + fig, ((ax0, ax1), (ax2, ax3)) = plt.subplots(figsize=(15, 14), nrows=2, ncols=2) + fig.suptitle(suptitle, fontsize=GLOBAL_FS_XLARGE, y=0.99) + + # Covariance matrix of the prior (normalised spectra theta) + ax0.set_aspect("equal") + ax0.set_xscale("log") + ax0.set_yscale("log") + ax0.xaxis.set_ticks_position("both") + ax0.yaxis.set_ticks_position("both") + ax0.xaxis.set_tick_params(which="both", direction="in", width=1.0) + ax0.xaxis.set_tick_params(which="major", length=6, labelsize=GLOBAL_FS) + ax0.xaxis.set_tick_params(which="minor", length=4) + ax0.yaxis.set_tick_params(which="both", direction="in", width=1.0) + ax0.yaxis.set_tick_params(which="major", length=6, labelsize=GLOBAL_FS) + ax0.yaxis.set_tick_params(which="minor", length=4) + divider = make_axes_locatable(ax0) + ax0_cb = divider.new_horizontal(size="5%", pad=0.10) + im0 = ax0.pcolormesh(X, Y, prior_theta_covariance[:-1, :-1], cmap="Blues", shading="flat") + for i in range(len(k_s)): + ax0.plot( + (k_s[i], k_s[i]), + (k_s.min(), k_s.max()), + linestyle=":", + linewidth=0.5, + color="green", + alpha=0.5, + ) + for i in range(len(k_s)): + ax0.plot( + (k_s.min(), k_s.max()), + (k_s[i], k_s[i]), + linestyle=":", + linewidth=0.5, + color="green", + alpha=0.5, + ) + ax0.set_title("$\\textbf{S}$", size=GLOBAL_FS_XLARGE) + ax0.set_xlabel("$k$ [$h$/Mpc]", size=GLOBAL_FS_XLARGE) + ax0.set_ylabel("$k$ [$h$/Mpc]", size=GLOBAL_FS_XLARGE) + fig.add_axes(ax0_cb) + cbar0 = fig.colorbar(im0, cax=ax0_cb) + cbar0.ax.tick_params(axis="y", direction="in", width=1.0, length=6, labelsize=GLOBAL_FS) + + # Covariance matrix of the prior (unnormalized spectra) + ax1.set_aspect("equal") + ax1.set_xscale("log") + ax1.set_yscale("log") + ax1.xaxis.set_ticks_position("both") + ax1.yaxis.set_ticks_position("both") + ax1.xaxis.set_tick_params(which="both", direction="in", width=1.0) + ax1.xaxis.set_tick_params(which="major", length=6, labelsize=GLOBAL_FS) + ax1.xaxis.set_tick_params(which="minor", length=4) + ax1.yaxis.set_tick_params(which="both", direction="in", width=1.0) + ax1.yaxis.set_tick_params(which="major", length=6, labelsize=GLOBAL_FS) + ax1.yaxis.set_tick_params(which="minor", length=4) + divider = make_axes_locatable(ax1) + ax1_cb = divider.new_horizontal(size="5%", pad=0.10) + im1 = ax1.pcolormesh(X, Y, prior_covariance[:-1, :-1], cmap="Purples", shading="flat") + for i in range(len(k_s)): + ax1.plot( + (k_s[i], k_s[i]), + (k_s.min(), k_s.max()), + linestyle=":", + linewidth=0.5, + color="green", + alpha=0.5, + ) + for i in range(len(k_s)): + ax1.plot( + (k_s.min(), k_s.max()), + (k_s[i], k_s[i]), + linestyle=":", + linewidth=0.5, + color="green", + alpha=0.5, + ) + ax1.set_title( + "$\\mathrm{diag}(\\textbf{P}_0) \\cdot \\textbf{S} \\cdot \\mathrm{diag}(\\textbf{P}_0)$", + size=GLOBAL_FS_XLARGE, + ) + ax1.set_xlabel("$k$ [$h$/Mpc]", size=GLOBAL_FS_XLARGE) + ax1.set_ylabel("$k$ [$h$/Mpc]", size=GLOBAL_FS_XLARGE) + fig.add_axes(ax1_cb) + cbar1 = fig.colorbar(im1, cax=ax1_cb) + cbar1.ax.tick_params(axis="y", direction="in", width=1.0, length=6, labelsize=GLOBAL_FS) + + posterior_covariance = np.diag(P_0).dot(posterior_theta_covariance).dot(np.diag(P_0)) + + # Covariance matrix of the posterior (normalised spectra theta) + ax2.set_aspect("equal") + ax2.set_xscale("log") + ax2.set_yscale("log") + ax2.xaxis.set_ticks_position("both") + ax2.yaxis.set_ticks_position("both") + ax2.xaxis.set_tick_params(which="both", direction="in", width=1.0) + ax2.xaxis.set_tick_params(which="major", length=6, labelsize=GLOBAL_FS) + ax2.xaxis.set_tick_params(which="minor", length=4) + ax2.yaxis.set_tick_params(which="both", direction="in", width=1.0) + ax2.yaxis.set_tick_params(which="major", length=6, labelsize=GLOBAL_FS) + ax2.yaxis.set_tick_params(which="minor", length=4) + divider = make_axes_locatable(ax2) + ax2_cb = divider.new_horizontal(size="5%", pad=0.10) + quantity = posterior_theta_covariance + vmin = quantity.min() + vmax = quantity.max() + if vmin < 0 and vmax > 0: + centerval = 0 + else: + centerval = np.mean(quantity) + norm_posterior = colors.TwoSlopeNorm( + vmin=posterior_theta_covariance.min(), + vcenter=centerval, + vmax=posterior_theta_covariance.max(), + ) + Blues_Reds = create_colormap("Blues_Reds") + im2 = ax2.pcolormesh( + X, + Y, + posterior_theta_covariance[:-1, :-1], + cmap=Blues_Reds, + norm=norm_posterior, + shading="flat", + ) + for i in range(len(k_s)): + ax2.plot( + (k_s[i], k_s[i]), + (k_s.min(), k_s.max()), + linestyle=":", + linewidth=0.5, + color="green", + alpha=0.5, + ) + for i in range(len(k_s)): + ax2.plot( + (k_s.min(), k_s.max()), + (k_s[i], k_s[i]), + linestyle=":", + linewidth=0.5, + color="green", + alpha=0.5, + ) + ax2.set_title("$\\boldsymbol{\\Gamma}$", size=GLOBAL_FS_XLARGE) + ax2.set_xlabel("$k$ [$h$/Mpc]", size=GLOBAL_FS_XLARGE) + ax2.set_ylabel("$k$ [$h$/Mpc]", size=GLOBAL_FS_XLARGE) + fig.add_axes(ax2_cb) + cbar2 = fig.colorbar(im2, cax=ax2_cb) + cbar2.ax.tick_params(axis="y", direction="in", width=1.0, length=6, labelsize=GLOBAL_FS) + cbar2.mappable.set_clim( + vmin=posterior_theta_covariance.min(), vmax=posterior_theta_covariance.max() + ) + if posterior_theta_covariance.min() < 0 and posterior_theta_covariance.max() > 0: + ticks = np.concatenate( + [ + np.linspace(posterior_theta_covariance.min(), 0, 5), + np.linspace(0, posterior_theta_covariance.max(), 5)[1:], + ] + ) + else: + ticks = np.linspace( + posterior_theta_covariance.min(), posterior_theta_covariance.max(), 6 + ) + ticks_labels = ["{:.1e}".format(tick) for tick in ticks] + cbar2.set_ticks(ticks) + cbar2.set_ticklabels(ticks_labels) + + # Covariance matrix of the posterior (unnormalised spectra) + ax3.set_aspect("equal") + ax3.set_xscale("log") + ax3.set_yscale("log") + ax3.xaxis.set_ticks_position("both") + ax3.yaxis.set_ticks_position("both") + ax3.xaxis.set_tick_params(which="both", direction="in", width=1.0) + ax3.xaxis.set_tick_params(which="major", length=6, labelsize=GLOBAL_FS) + ax3.xaxis.set_tick_params(which="minor", length=4) + ax3.yaxis.set_tick_params(which="both", direction="in", width=1.0) + ax3.yaxis.set_tick_params(which="major", length=6, labelsize=GLOBAL_FS) + ax3.yaxis.set_tick_params(which="minor", length=4) + divider = make_axes_locatable(ax3) + ax3_cb = divider.new_horizontal(size="5%", pad=0.10) + quantity = posterior_covariance + vmin = quantity.min() + vmax = quantity.max() + if vmin < 0 and vmax > 0: + centerval = 0 + else: + centerval = np.mean(quantity) + norm_posterior_spectrum = colors.TwoSlopeNorm( + vmin=posterior_covariance.min(), + vcenter=centerval, + vmax=posterior_covariance.max(), + ) + Purples_Oranges = create_colormap("Purples_Oranges") + im3 = ax3.pcolormesh( + X, + Y, + posterior_covariance[:-1, :-1], + cmap=Purples_Oranges, + norm=norm_posterior_spectrum, + shading="flat", + ) + for i in range(len(k_s)): + ax3.plot( + (k_s[i], k_s[i]), + (k_s.min(), k_s.max()), + linestyle=":", + linewidth=0.5, + color="green", + alpha=0.5, + ) + for i in range(len(k_s)): + ax3.plot( + (k_s.min(), k_s.max()), + (k_s[i], k_s[i]), + linestyle=":", + linewidth=0.5, + color="green", + alpha=0.5, + ) + ax3.set_title( + "$\\mathrm{diag}(\\textbf{P}_0) \\cdot \\boldsymbol{\\Gamma} \\cdot \\mathrm{diag}(\\textbf{P}_0)$", + size=22, + ) + ax3.set_xlabel("$k$ [$h$/Mpc]", size=22) + ax3.set_ylabel("$k$ [$h$/Mpc]", size=22) + fig.add_axes(ax3_cb) + cbar3 = fig.colorbar(im3, cax=ax3_cb) + cbar3.ax.tick_params(axis="y", direction="in", width=1.0, length=6, labelsize=19) + cbar3.ax.tick_params(axis="y", direction="in", width=1.0, length=6, labelsize=19) + cbar3.mappable.set_clim(vmin=posterior_covariance.min(), vmax=posterior_covariance.max()) + if posterior_covariance.min() < 0 and posterior_covariance.max() > 0: + ticks = np.concatenate( + [ + np.linspace(posterior_covariance.min(), 0, 5), + np.linspace(0, posterior_covariance.max(), 5)[1:], + ] + ) + else: + ticks = np.linspace(posterior_covariance.min(), posterior_covariance.max(), 6) + ticks_labels = ["{:.1e}".format(tick) for tick in ticks] + cbar3.set_ticks(ticks) + cbar3.set_ticklabels(ticks_labels) + + fig.tight_layout() + if savepath is not None: + fig.savefig(savepath, bbox_inches="tight", dpi=300, format="png", transparent=True) + fig.savefig(savepath[:-4] + ".pdf", bbox_inches="tight", dpi=300, format="pdf") + fig.suptitle("") + fig.savefig( + savepath[:-4] + "_notitle.png", + bbox_inches="tight", + dpi=300, + format="png", + transparent=True, + ) + fig.savefig(savepath[:-4] + "_notitle.pdf", bbox_inches="tight", dpi=300, format="pdf") + logger.info("Plot saved to %s", savepath) + if force_plot or not savepath: + plt.show() + + except Exception as e: + logger.critical("Unexpected error during plotting: %s", str(e)) + raise RuntimeError("Plotting failed.") from e + finally: + plt.close(fig) + del fig + gc.collect() + + +def plot_gradients( + Pbins, + P, + df_16_full, + df_32_full, + df_48_full, + df_full, + k_s, + X, + Y, + fixscale=False, + force=False, + suptitle="", + savepath=None, +): + """ + Plot gradients. + + Parameters + ---------- + Pbins : ndarray + Vector of bin boundaries for the summary statistics. + P : int + Number of bins. + df_16_full : ndarray + Derivative with respect to the 16th input. + df_32_full : ndarray + Derivative with respect to the 32nd input. + df_48_full : ndarray + Derivative with respect to the 48th input. + df_full : ndarray + Full derivative. + k_s : ndarray + Wavenumbers. + X : ndarray + X-axis grid. + Y : ndarray + Y-axis grid. + fixscale : bool, optional + Fix the y-axis scale. Default is False. + force : bool, optional + Display plot even if savepath is set. + suptitle : str, optional + Title for the plot. + savepath : str or Path, optional + Path to save the plot. If None, the plot is displayed. + + Raises + ------ + RuntimeError + If unexpected errors occur during plotting. + + """ + logger.info("Plotting gradients...") + + try: + fig = plt.figure(figsize=(15, 12)) + fig.suptitle(suptitle, y=0.95, fontsize=22) + + gs0 = gridspec.GridSpec( + 3, + 2, + width_ratios=[1.0, 0.5], + height_ratios=[1.0, 1.0, 1.0], + hspace=0.0, + wspace=0.2, + ) + gs0.update(right=1.0, left=0.0) + ax00 = plt.subplot(gs0[0, 0]) + ax01 = plt.subplot(gs0[1, 0], sharex=ax00) + ax02 = plt.subplot(gs0[2, 0], sharex=ax00) + + gs1 = gridspec.GridSpec( + 3, + 2, + width_ratios=[1.0, 0.5], + height_ratios=[1.0, 1.0, 1.0], + hspace=0.0, + wspace=0.2, + ) + gs1.update(top=0.881, bottom=0.112) + ax10 = plt.subplot(gs1[0, 1]) + ax11 = plt.subplot(gs1[1, 1], sharex=ax10) + ax12 = plt.subplot(gs1[2, 1], sharex=ax10) + + axx = [(ax00, ax10), (ax01, ax11), (ax02, ax12)] + for axs, idx in zip(axx, range(3)): + ax = axs[0] + df_16 = np.copy(df_16_full[idx * P : (idx + 1) * P]) + df_32 = np.copy(df_32_full[idx * P : (idx + 1) * P]) + df_48 = np.copy(df_48_full[idx * P : (idx + 1) * P]) + df = df_full[idx * P : (idx + 1) * P] + + # Plot the three selected components of the derivative: + ax.set_xlim([Pbins.min() - 0.0001, Pbins.max() + 0.01]) + ax.semilogx(Pbins, np.zeros_like(Pbins), linestyle=":", color="black") + ax.semilogx( + Pbins, + df_16, + linewidth=2, + linestyle="-", + color="C4", + label=r"$(\nabla \mathbf{f}_0)^\intercal_{16}$", + zorder=2, + ) + ax.semilogx( + Pbins, + df_32, + linewidth=2, + linestyle="-", + color="C0", + label=r"$(\nabla \mathbf{f}_0)^\intercal_{32}$", + zorder=2, + ) + ax.semilogx( + Pbins, + df_48, + linewidth=2, + linestyle="-", + color="C2", + label=r"$(\nabla \mathbf{f}_0)^\intercal_{48}$", + zorder=2, + ) + if fixscale: + ymin = np.min([df_16, df_32, df_48]) - 1e-2 + ymax = np.max([df_16, df_32, df_48]) + 1e-2 + else: + (ymin, ymax) = ax.get_ylim() + ax.set_ylim([ymin, ymax]) + + # Binning + for i in range(len(Pbins)): + ax.plot( + (Pbins[i], Pbins[i]), + (ymin, ymax), + linestyle="--", + linewidth=0.8, + color="red", + alpha=0.5, + zorder=1, + ) + ax.yaxis.grid(linestyle=":", color="grey") + ax.set_ylabel("population " + str(idx + 1), size=21) + ax.xaxis.set_ticks_position("both") + ax.yaxis.set_ticks_position("both") + ax.xaxis.set_tick_params(which="both", direction="in", width=1.0) + ax.yaxis.set_tick_params(which="both", direction="in", width=1.0) + for axis in ["top", "bottom", "left", "right"]: + ax.spines[axis].set_linewidth(1.0) + ax.xaxis.set_tick_params(which="major", length=6) + ax.xaxis.set_tick_params(which="minor", length=4) + ax.yaxis.set_tick_params(which="major", length=6) + + # Plot the full gradient + ax1 = axs[1] + ax1.set_xlim([k_s.min(), k_s.max()]) + ax1.set_ylim([Pbins.min(), Pbins.max()]) + ax1.set_xscale("log") + ax1.set_yscale("log") + ax1.xaxis.set_ticks_position("both") + ax1.yaxis.set_ticks_position("both") + ax1.xaxis.set_tick_params(which="both", direction="in", width=1.0) + ax1.xaxis.set_tick_params(which="major", length=6) + ax1.xaxis.set_tick_params(which="minor", length=4) + ax1.yaxis.set_tick_params(which="both", direction="in", width=1.0) + ax1.yaxis.set_tick_params(which="major", length=6) + ax1.yaxis.set_tick_params(which="minor", length=4) + quantity = df + vmin = quantity.min() + vmax = quantity.max() + if vmin < 0 and vmax > 0: + centerval = 0 + else: + centerval = np.mean(quantity) + norm_grad = colors.TwoSlopeNorm(vmin=vmin, vcenter=centerval, vmax=vmax) + GradientMap = create_colormap("GradientMap") + im1 = ax1.pcolormesh( + X, Y, df[:-1, :-1], cmap=GradientMap, shading="flat", norm=norm_grad + ) + ax1.plot(k_s, k_s, color="grey", linestyle="--") + for i in range(len(k_s)): + ax1.plot( + (k_s[i], k_s[i]), + (Pbins.min(), Pbins.max()), + linestyle=":", + linewidth=0.5, + color="green", + alpha=0.5, + ) + for i in range(len(Pbins)): + ax1.plot( + (k_s.min(), k_s.max()), + (Pbins[i], Pbins[i]), + linestyle="--", + linewidth=0.5, + color="red", + alpha=0.5, + ) + ax1.set_ylabel(r"$k$ [$h$/Mpc]", size=17) + cbar1 = fig.colorbar(im1, shrink=0.9, pad=0.01) + cbar1.ax.tick_params(axis="y", direction="in", width=1.0, length=6) + ticks = np.concatenate([np.linspace(df.min(), 0, 5), np.linspace(0, df.max(), 5)[1:]]) + cbar1.set_ticks(ticks) + + if idx == 0: + ax.legend(fontsize=21, loc="upper left") + ax1.set_title(r"Full gradient $\nabla \mathbf{f}_0$", size=21) + elif idx == 2: + ax.set_xlabel(r"$k$ [$h$/Mpc]", size=21) + ax1.set_xlabel(r"$k$ [$h$/Mpc]", size=21) + + if savepath is None or force: + plt.show() + if savepath is not None: + fig.savefig(savepath, bbox_inches="tight", dpi=300, format="png", transparent=True) + fig.savefig(savepath[:-4] + ".pdf", bbox_inches="tight", dpi=300, format="pdf") + fig.suptitle("") + fig.savefig( + savepath[:-4] + "_notitle.png", + bbox_inches="tight", + dpi=300, + format="png", + transparent=True, + ) + fig.savefig(savepath[:-4] + "_notitle.pdf", bbox_inches="tight", dpi=300, format="pdf") + + logger.info("Plot saved to %s", savepath) + + except Exception as e: + logger.critical("Unexpected error during plotting: %s", str(e)) + raise RuntimeError("Plotting failed.") from e + finally: + plt.close(fig) + del fig + gc.collect() + + +def plot_mocks( + NORM, + N, + P, + Pbins, + phi_obs, + Phi_0, + f_0, + C_0, + X, + Y, + CMap, + suptitle=None, + force_plot=False, + savepath=None, +): + """ + Plot mocks. + + Parameters + ---------- + NORM : float + Normalisation factor. + N : int + Number of mocks. + P : int + Number of bins. + Pbins : ndarray + Vector of bin boundaries for the summary statistics. + phi_obs : ndarray + Observed power spectrum. + Phi_0 : ndarray + Mock power spectra. + f_0 : ndarray + Averaged power spectrum. + C_0 : ndarray + Covariance matrix. + X : ndarray + X-axis grid. + Y : ndarray + Y-axis grid. + CMap : str + Colormap. + suptitle : str, optional + Title for the plot. + force_plot : bool, optional + Display plot even if savepath is set. + savepath : str or Path, optional + Path to save the plot. If None, the plot is displayed. + + Raises + ------ + RuntimeError + If unexpected errors occur during plotting. + + """ + logger.info("Plotting mocks and intra-population covariance...") + + alpha_mocks = 0.25 + alpha_binning = 0.3 + try: + Phi_0_full = Phi_0.copy() + phi_obs_full = phi_obs.copy() + f_0_full = f_0.copy() + C_0_full = C_0.copy() + + idx = 0 + Phi_0 = Phi_0[:, idx * P : (idx + 1) * P] + phi_obs = phi_obs[idx * P : (idx + 1) * P] + f_0 = f_0[idx * P : (idx + 1) * P] + C_0 = C_0[idx * P : (idx + 1) * P, idx * P : (idx + 1) * P] + + COLOUR_LIST_means = ["darkorchid", "saddlebrown", "mediumvioletred"] + + fig = plt.figure(figsize=(15.5, 10)) + gs0 = gridspec.GridSpec( + 3, + 3, + width_ratios=[1.0, 1.0, 1.0], + height_ratios=[1.0, 1.0, 1.0], + wspace=0.0, + hspace=0.0, + ) + gs0.update(right=1.0, left=0.0) + ax0 = plt.subplot(gs0[0, 0]) + ax0b = plt.subplot(gs0[0, 1]) + ax01 = plt.subplot(gs0[1, 0], sharex=ax0) + ax01b = plt.subplot(gs0[1, 1]) + ax02 = plt.subplot(gs0[2, 0], sharex=ax0) + ax02b = plt.subplot(gs0[2, 1]) + + axx0x = [[ax01, ax01b], [ax02, ax02b]] + + # Observed power spectrum (normalised) + ax0.semilogx( + Pbins, + phi_obs * NORM, + linewidth=2, + color="black", + label=r"$\boldsymbol{\Phi}_\mathrm{O}$", + zorder=3, + ) + + # Plot the Ne realisations and the average value + for i in range(N - 1): + ax0.semilogx(Pbins, Phi_0[i], color="C7", alpha=alpha_mocks, linewidth=0.7) + ax0.semilogx( + Pbins, + Phi_0[N - 1], + color="C7", + alpha=alpha_mocks, + linewidth=0.7, + ) + ax0.semilogx( + Pbins, + f_0, + linewidth=2, + color=COLOUR_LIST_means[idx], + linestyle="--", + label=r"$\textbf{f}_0$", + zorder=2, + ) + + # 2-sigma intervals + ax0.fill_between( + Pbins, + f_0 - 2 * np.sqrt(np.diag(C_0)), + f_0 + 2 * np.sqrt(np.diag(C_0)), + color=COLOUR_LIST[idx], + alpha=0.4, + label=r"2 $\sqrt{\mathrm{diag}(\textbf{C}_0)}$", + zorder=2, + ) + + # Plot the binning + (ymin, ymax) = ax0.get_ylim() + ax0.set_ylim([ymin, ymax]) + for i in range(len(Pbins)): + ax0.plot( + (Pbins[i], Pbins[i]), + (ymin, ymax), + linestyle="--", + linewidth=0.8, + color="red", + alpha=alpha_binning, + zorder=1, + ) + + ax0.set_xlim([Pbins.min() - 0.0001, Pbins.max() + 0.01]) + ax0.set_ylabel("population 1", size=GLOBAL_FS) + ax0.legend(fontsize=GLOBAL_FS, loc="upper right") + ax0.xaxis.set_ticks_position("both") + ax0.xaxis.set_tick_params(which="both", direction="in", width=1.0) + for axis in ["top", "bottom", "left", "right"]: + ax0.spines[axis].set_linewidth(1.0) + ax0.xaxis.set_tick_params(which="major", length=6) + ax0.xaxis.set_tick_params(which="minor", length=4) + if suptitle is not None: + ax0.set_title( + r"$\boldsymbol{\Phi}$ (" + suptitle + ")", y=1.05, fontsize=GLOBAL_FS_LARGE + ) + else: + ax0.set_title(r"$\boldsymbol{\Phi}$", y=1.05, fontsize=GLOBAL_FS_LARGE) + ax0.tick_params(axis="y", which="major", labelsize=GLOBAL_FS_TINY) + ax0.yaxis.set_major_formatter(FormatStrFormatter("%.1f")) + + # Same as above but normalise everything by the observations: + normalisation = phi_obs + + ax0b.semilogx( + Pbins, + NORM * phi_obs / normalisation, + linewidth=2, + color="black", + label=r"$\boldsymbol{\Phi}_\mathrm{O}$", + zorder=3, + ) + + for i in range(N - 1): + ax0b.semilogx( + Pbins, + Phi_0[i] / normalisation, + color="C7", + alpha=alpha_mocks, + linewidth=0.7, + ) + + ax0b.semilogx( + Pbins, + Phi_0[N - 1] / normalisation, + color="C7", + alpha=alpha_mocks, + linewidth=0.7, + label=r"$\boldsymbol{\Phi}_{\theta_0}$", + ) + + ax0b.semilogx( + Pbins, + f_0 / normalisation, + linewidth=2, + color=COLOUR_LIST_means[idx], + linestyle="--", + label=r"$\textbf{f}_0$", + zorder=2, + ) + + # 2-sigma intervals + ax0b.fill_between( + Pbins, + f_0 / normalisation - 2 * np.sqrt(np.diag(C_0)) / normalisation, + f_0 / normalisation + 2 * np.sqrt(np.diag(C_0)) / normalisation, + color=COLOUR_LIST[idx], + alpha=0.4, + label=r"2 $\sqrt{\mathrm{diag}(\textbf{C}_0)}$", + zorder=2, + ) + + # Plot the binning + (ymin, ymax) = ax0b.get_ylim() + ax0b.set_ylim([ymin, ymax]) + for i in range(len(Pbins)): + ax0b.plot( + (Pbins[i], Pbins[i]), + (ymin, ymax), + linestyle="--", + linewidth=0.8, + color="red", + alpha=alpha_binning, + zorder=1, + ) + + ax0b.set_xlim([Pbins.min() - 0.0001, Pbins.max() + 0.01]) + ax0b.set_xlabel(r"$k$ [$h$/Mpc]", size=GLOBAL_FS) + ax0b.xaxis.set_ticks_position("both") + ax0b.xaxis.set_tick_params(which="both", direction="in", width=1.0) + for axis in ["top", "bottom", "left", "right"]: + ax0b.spines[axis].set_linewidth(1.0) + ax0b.xaxis.set_tick_params(which="major", length=6) + ax0b.xaxis.set_tick_params(which="minor", length=4) + ax0b.tick_params(axis="y", which="major", labelsize=GLOBAL_FS_TINY) + ax0b.yaxis.tick_right() + if suptitle is not None: + ax0b.set_title( + r"$\boldsymbol{\Phi}/\boldsymbol{\Phi}_\mathrm{O}$ (" + suptitle + ")", + y=1.05, + fontsize=GLOBAL_FS, + ) + else: + ax0b.set_title( + r"$\boldsymbol{\Phi}/\boldsymbol{\Phi}_\mathrm{O}$", y=1.05, fontsize=GLOBAL_FS + ) + + for ax, axb in axx0x: + idx += 1 + Phi_0 = Phi_0_full[:, idx * P : (idx + 1) * P] + phi_obs = phi_obs_full[idx * P : (idx + 1) * P] + f_0 = f_0_full[idx * P : (idx + 1) * P] + C_0 = C_0_full[idx * P : (idx + 1) * P, idx * P : (idx + 1) * P] + + # Same plot as above but for the other axes: + ax.set_xlim([Pbins.min() - 0.0001, Pbins.max() + 0.01]) + ax.semilogx(Pbins, phi_obs, linewidth=2, color="black", zorder=3) + for i in range(N - 1): + ax.semilogx(Pbins, Phi_0[i], color="C7", alpha=alpha_mocks, linewidth=0.7) + ax.semilogx(Pbins, Phi_0[N - 1], color="C7", alpha=alpha_mocks, linewidth=0.7) + ax.semilogx( + Pbins, + f_0, + linewidth=2, + color=COLOUR_LIST_means[idx], + linestyle="--", + label=r"$\textbf{f}_0$", + zorder=2, + ) + + # 2-sigma intervals + ax.fill_between( + Pbins, + f_0 - 2 * np.sqrt(np.diag(C_0)), + f_0 + 2 * np.sqrt(np.diag(C_0)), + color=COLOUR_LIST[idx], + alpha=0.4, + label=r"2 $\sqrt{\mathrm{diag}(\textbf{C}_0)}$", + zorder=2, + ) + + # Plot the binning + (ymin, ymax) = ax.get_ylim() + ax.set_ylim([ymin, ymax]) + for i in range(len(Pbins)): + ax.plot( + (Pbins[i], Pbins[i]), + (ymin, ymax), + linestyle="--", + linewidth=0.8, + color="red", + alpha=alpha_binning, + zorder=1, + ) + ax.set_ylabel("population " + str(idx + 1), size=GLOBAL_FS) + ax.set_xlabel(r"$k$ [$h$/Mpc]", size=GLOBAL_FS) + ax.tick_params(axis="x", which="major", labelsize=GLOBAL_FS_SMALL, pad=8) + ax.xaxis.set_ticks_position("both") + ax.xaxis.set_tick_params(which="both", direction="in", width=1.0) + ax.tick_params(axis="y", which="major", labelsize=GLOBAL_FS_TINY) + ax.yaxis.set_major_formatter(FormatStrFormatter("%.1f")) + for axis in ["top", "bottom", "left", "right"]: + ax.spines[axis].set_linewidth(1.0) + ax.xaxis.set_tick_params(which="major", length=6) + ax.xaxis.set_tick_params(which="minor", length=4) + ax.legend(loc="upper right", fontsize=GLOBAL_FS) + + # Same as above but normalise everything by the observations: + normalisation = phi_obs + + # Observed power spectrum + axb.semilogx( + Pbins, + phi_obs / normalisation, + linewidth=2, + color="black", + label=r"$\boldsymbol{\Phi}_\mathrm{O}$", + zorder=3, + ) + + for i in range(N - 1): + axb.semilogx( + Pbins, + Phi_0[i] / normalisation, + color="C7", + alpha=alpha_mocks, + linewidth=0.7, + ) + + axb.semilogx( + Pbins, + Phi_0[N - 1] / normalisation, + color="C7", + alpha=alpha_mocks, + linewidth=0.7, + label=r"$\boldsymbol{\Phi}_{\theta_0}$", + ) + axb.semilogx( + Pbins, + f_0 / normalisation, + linewidth=2, + color=COLOUR_LIST_means[idx], + linestyle="--", + label=r"$\textbf{f}_0$", + zorder=2, + ) + + # 2-sigma intervals + axb.fill_between( + Pbins, + f_0 / normalisation - 2 * np.sqrt(np.diag(C_0)) / normalisation, + f_0 / normalisation + 2 * np.sqrt(np.diag(C_0)) / normalisation, + color=COLOUR_LIST[idx], + alpha=0.4, + label=r"2 $\sqrt{\mathrm{diag}(\textbf{C}_0)}$", + zorder=2, + ) + + # Plot the binning + (ymin, ymax) = axb.get_ylim() + axb.set_ylim([ymin, ymax]) + for i in range(len(Pbins)): + axb.plot( + (Pbins[i], Pbins[i]), + (ymin, ymax), + linestyle="--", + linewidth=0.8, + color="red", + alpha=alpha_binning, + zorder=1, + ) + + axb.set_xlim([Pbins.min() - 0.0001, Pbins.max() + 0.01]) + axb.set_xlabel(r"$k$ [$h$/Mpc]", size=GLOBAL_FS) + axb.tick_params(axis="x", which="major", labelsize=GLOBAL_FS_SMALL, pad=8) + axb.xaxis.set_ticks_position("both") + axb.xaxis.set_tick_params(which="both", direction="in", width=1.0) + axb.tick_params(axis="y", which="major", labelsize=GLOBAL_FS_TINY) + axb.yaxis.tick_right() + for axis in ["top", "bottom", "left", "right"]: + axb.spines[axis].set_linewidth(1.0) + axb.xaxis.set_tick_params(which="major", length=6) + axb.xaxis.set_tick_params(which="minor", length=4) + + axx1x = [plt.subplot(gs0[0, 2]), plt.subplot(gs0[1, 2]), plt.subplot(gs0[2, 2])] + + # Diagonal blocks of the covariance matrix (intra-population covariance) + idx = 0 + for ax1 in axx1x: + Phi_0 = Phi_0_full[:, idx * P : (idx + 1) * P] + phi_obs = phi_obs_full[idx * P : (idx + 1) * P] + f_0 = f_0_full[idx * P : (idx + 1) * P] + C_0 = C_0_full[idx * P : (idx + 1) * P, idx * P : (idx + 1) * P] + idx += 1 + + C0min = C_0.min() + C0max = C_0.max() + if C0min < 0 and C0max > 0: + centerval = 0 + else: + centerval = np.mean(C_0) + divnorm = colors.TwoSlopeNorm(vmin=C_0.min(), vcenter=centerval, vmax=C_0.max()) + + # Plot the current block of the covariance matrix + im1 = ax1.pcolormesh(X, Y, C_0[:-1, :-1], shading="flat", norm=divnorm, cmap=CMap) + + # Plot the binning + for i in range(len(Pbins)): + ax1.plot( + (Pbins[i], Pbins[i]), + (Pbins.min(), Pbins.max()), + linestyle="--", + linewidth=0.5, + color="red", + alpha=0.5, + ) + for i in range(len(Pbins)): + ax1.plot( + (Pbins.min(), Pbins.max()), + (Pbins[i], Pbins[i]), + linestyle="--", + linewidth=0.5, + color="red", + alpha=0.5, + ) + if idx == 1: + ax1.set_title(r"diagonal blocks of $\textbf{C}_0$", size=GLOBAL_FS, y=1.05) + ax1.set_xlabel(r"$k$ [$h$/Mpc]", size=GLOBAL_FS) + ax1.set_ylabel(r"$k$ [$h$/Mpc]", size=GLOBAL_FS_SMALL) + + ax1.set_aspect("equal") + ax1.set_xscale("log") + ax1.set_yscale("log") + ax1.xaxis.set_ticks_position("both") + ax1.yaxis.set_ticks_position("both") + ax1.xaxis.set_tick_params(which="both", direction="in", width=1.0) + ax1.xaxis.set_tick_params(which="major", length=6) + ax1.xaxis.set_tick_params(which="minor", length=4) + ax1.yaxis.set_tick_params(which="both", direction="in", width=1.0) + ax1.yaxis.set_tick_params(which="major", length=6) + ax1.yaxis.set_tick_params(which="minor", length=4) + ax1.tick_params(axis="x", which="major", labelsize=GLOBAL_FS_SMALL, pad=8) + ax1.tick_params(axis="y", which="major", labelsize=GLOBAL_FS_TINY) + + cbar1 = fig.colorbar(im1, shrink=0.9, pad=0.02, format="%.1e") + cbar1.ax.tick_params(axis="y", direction="in", width=1.0, length=4) + cbar1.ax.yaxis.set_tick_params(labelsize=GLOBAL_FS_SMALL) + cbar1.update_normal(im1) + vmin = C_0[:-1, :-1].min() + vmax = C_0[:-1, :-1].max() + cbar1.mappable.set_clim(vmin=vmin, vmax=vmax) + if vmin < 0 and vmax > 0: + ticks = np.concatenate( + [ + np.linspace(vmin * 0.8, 0, 2), + np.linspace(0, 0.8 * vmax, 2)[1:], + ] + ) + else: + ticks = np.linspace(vmin, vmax, 5) + cbar1.set_ticks(ticks) + formatter = ScalarFormatter(useMathText=True) + formatter.set_powerlimits((0, 0)) + cbar1.ax.yaxis.set_major_formatter(formatter) + cbar1.ax.yaxis.set_major_formatter(FormatStrFormatter("%.1f")) + cbar1.ax.yaxis.get_offset_text().set_fontsize(GLOBAL_FS_TINY) + cbar1.update_ticks() + + if savepath is not None: + savepath = Path(savepath) + fig.savefig(savepath, bbox_inches="tight", transparent=True, dpi=300, format="png") + fig.savefig(savepath.with_suffix(".pdf"), bbox_inches="tight", dpi=300, format="pdf") + logger.info("Plot saved to %s", savepath) + if force_plot or not savepath: + plt.show() + + except Exception as e: + logger.critical("Unexpected error during plotting: %s", str(e)) + raise RuntimeError("Plotting failed.") from e + + +def plot_histogram( + data, + recmahal, + suptitle, + savepath, + bins=30, + alpha=0.5, + color="blue", +): + """ + Plot a Mahalanobis distance histogram with key reference lines. + + Parameters + ---------- + data : array-like + Collection of Mahalanobis distances. + recmahal : float + Single Mahalanobis distance for the reconstruction + (e.g. posterior vs. prior mean). + suptitle : str + Figure title. + savepath : str + Full path (including filename) to save the resulting plot. + bins : int, optional + Number of bins for the histogram. Default is 30. + alpha : float, optional + Transparency level for the histogram bars. Default is 0.5. + color : str, optional + Colour of the histogram bars. Default is "blue". + + Raises + ------ + OSError + If the file cannot be saved to the specified path. + RuntimeError + For unexpected plotting failures. + """ + try: + logger.debug("Starting plot_histogram with data of size %d.", len(data)) + + plt.figure(figsize=(8, 5)) + plt.hist(data, bins=bins, alpha=alpha, color=color) + + labrec = r"$d_\mathrm{M}(\boldsymbol{\gamma}_{\textrm{rec}}, \boldsymbol{\theta}_0 \mid \textbf{S})$" + plt.axvline(recmahal, color="black", linestyle="-", linewidth=2, label=labrec) + + labx = r"$d_\mathrm{M}(\boldsymbol{\gamma}, \boldsymbol{\theta}_0 \mid \textbf{S})$" + labmean = r"$\langle d_\mathrm{M}(\boldsymbol{\gamma}, \boldsymbol{\theta}_0 \mid \textbf{S}) \rangle$" + plt.axvline( + np.mean(data), + color="tab:pink", + linestyle="-", + linewidth=2, + label=labmean, + ) + std = np.std(data) + labstd = r"$\pm 1 \sigma$" + plt.axvline( + np.mean(data) + std, + color="pink", + linestyle="--", + linewidth=1, + label=labstd, + ) + plt.axvline( + np.mean(data) - std, + color="pink", + linestyle="--", + linewidth=1, + ) + + plt.xlabel(labx, fontsize=GLOBAL_FS_LARGE) + plt.ylabel("Density", fontsize=GLOBAL_FS_LARGE) + plt.xticks(fontsize=GLOBAL_FS) + plt.yticks([]) + plt.suptitle(suptitle, fontsize=GLOBAL_FS_XLARGE) + plt.legend(fontsize=GLOBAL_FS_LARGE) + + plt.savefig(savepath, bbox_inches="tight", dpi=300, transparent=True) + plt.savefig(savepath[:-4] + ".pdf", bbox_inches="tight", dpi=300) + logger.info("Histogram plot saved to %s and %s.pdf", savepath, savepath[:-4]) + plt.close() + + except OSError as e: + logger.error("File saving failed at path '%s': %s", savepath, str(e)) + raise + except Exception as e: + logger.critical("Unexpected error during histogram plotting: %s", str(e)) + raise RuntimeError("plot_histogram failed.") from e + finally: + gc.collect() + logger.debug("plot_histogram: memory cleanup done.") diff --git a/src/selfisys/utils/timestepping.py b/src/selfisys/utils/timestepping.py new file mode 100644 index 0000000..49e6aa2 --- /dev/null +++ b/src/selfisys/utils/timestepping.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +"""Tools for time-stepping. +""" + + +def merge_nTS(ts_path_list, merged_path): + """ + Merge multiple time-stepping objects into a single file. + + Parameters + ---------- + ts_path_list : list of str + Paths to the individual time-stepping files to be merged. + merged_path : str + Path to save the merged time-stepping file. + + Returns + ------- + None + """ + from h5py import File + from numpy import concatenate + from pysbmy.timestepping import read_timestepping + + # Read individual time-stepping objects + ts = [read_timestepping(ts_path) for ts_path in ts_path_list] + + with File(merged_path, "w") as hf: + # Write scalar attributes + hf.attrs["/info/scalars/nsteps"] = sum(tsi.nsteps for tsi in ts) + hf.attrs["/info/scalars/nkicks"] = sum(tsi.nkicks for tsi in ts) + hf.attrs["/info/scalars/ndrifts"] = sum(tsi.ndrifts for tsi in ts) + hf.attrs["/info/scalars/ai"] = ts[0].ai + hf.attrs["/info/scalars/af"] = ts[-1].af + + # Merge and write datasets + hf.create_dataset("/scalars/forces", data=concatenate([tsi.forces for tsi in ts])) + hf.create_dataset("/scalars/snapshots", data=concatenate([tsi.snapshots for tsi in ts])) + hf.create_dataset("/scalars/aKickBeg", data=concatenate([tsi.aKickBeg for tsi in ts])) + hf.create_dataset("/scalars/aKickEnd", data=concatenate([tsi.aKickEnd for tsi in ts])) + hf.create_dataset("/scalars/aDriftBeg", data=concatenate([tsi.aDriftBeg for tsi in ts])) + hf.create_dataset("/scalars/aDriftEnd", data=concatenate([tsi.aDriftEnd for tsi in ts])) + hf.create_dataset("/scalars/aiKick", data=concatenate([tsi.aiKick for tsi in ts])) + hf.create_dataset("/scalars/afKick", data=concatenate([tsi.afKick for tsi in ts])) + + # Handle `aDrift` merging with overlap adjustments + aDrift_data = concatenate( + [ + [ts[0].aDrift[0]], # Initial drift + concatenate( + [concatenate([tsi.aDrift[1:], [tsi.aDrift[-1]]]) for tsi in ts[:-1]] + ), # Intermediate drifts + ts[-1].aDrift[1:], # Final drift + ] + ) + hf.create_dataset("/scalars/aDrift", data=aDrift_data) + + # Handle `aSnapshotSave` merging + aSnapshotSave_data = concatenate( + [ts[0].aSnapshotSave] + [tsi.aSnapshotSave[1:] for tsi in ts[1:]] + ) + hf.create_dataset("/scalars/aSnapshotSave", data=aSnapshotSave_data) + + hf.create_dataset("/scalars/aiDrift", data=concatenate([tsi.aiDrift for tsi in ts])) + hf.create_dataset("/scalars/afDrift", data=concatenate([tsi.afDrift for tsi in ts])) + hf.create_dataset("/scalars/aKick", data=concatenate([tsi.aKick for tsi in ts])) diff --git a/src/selfisys/utils/tools.py b/src/selfisys/utils/tools.py new file mode 100644 index 0000000..fc6a18f --- /dev/null +++ b/src/selfisys/utils/tools.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Utilities for the SelfiSys package, including tools for cosmological +parameter handling, power spectrum computations, and prior sampling. +""" + + +def none_or_bool_or_str(value): + """ + Convert string representations of None, True, and False to their + respective Python objects; otherwise, return the input value. + """ + if value == "None": + return None + if value == "True": + return True + if value == "False": + return False + return value + + +def get_k_max(L, size): + """ + Compute the maximum wavenumber for a given box size. + + Parameters + ---------- + L : float + Size of the box in Mpc/h. + size : int + Number of grid cells along each dimension. + + Returns + ------- + float + Maximum wavenumber in h/Mpc. + """ + from numpy import pi, sqrt + + return int(1e3 * sqrt(3) * pi * size / L + 1) * 1e-3 + + +def custom_stat(vec): + """ + Compute a custom statistic for use with + `scipy.stats.binned_statistic`. + + Assumes the data power spectrum is inverse-Gamma distributed (as in + [jasche2010bayesian] and [leclercq2019primordial]). Returns "NaN" + for vectors with insufficient elements, as expected by + `scipy.stats.binned_statistic`. + + Parameters + ---------- + vec : array-like + Input vector for computation. + + Returns + ------- + float or str + Custom statistic or NaN if input is invalid. + """ + if len(vec) <= 2 or sum(vec) == 0: + return "NaN" + return sum(vec) / (len(vec) - 2) + + +def cosmo_vector_to_Simbelmyne_dict(x, kmax=1.4): + """ + Convert a vector of cosmological parameters into a dictionary + compatible with `pysbmy`. + + Parameters + ---------- + x : array-like + Vector of cosmological parameters. + kmax : float, optional + Maximum wavenumber for the power spectrum computation. + + Returns + ------- + dict + Dictionary of cosmological parameters compatible with `pysbmy`. + """ + from selfisys.global_parameters import WHICH_SPECTRUM + + return { + "h": x[0], + "Omega_r": 0.0, + "Omega_q": 1.0 - x[2], + "Omega_b": x[1], + "Omega_m": x[2], + "m_ncdm": 0.0, + "Omega_k": 0.0, + "tau_reio": 0.066, + "n_s": x[3], + "sigma8": x[4], + "w0_fld": -1.0, + "wa_fld": 0.0, + "k_max": kmax, + "WhichSpectrum": WHICH_SPECTRUM, + } + + +def cosmo_vector_to_class_dict(x, lmax=2500, kmax=1.4): + """ + Convert a vector of cosmological parameters into a dictionary + compatible with `classy`. + + Parameters + ---------- + x : array-like + Vector of cosmological parameters. + lmax : int, optional + Maximum multipole for the power spectrum computation. + kmax : float, optional + Maximum wavenumber for the power spectrum computation. + + Returns + ------- + dict + Dictionary of cosmological parameters compatible with `classy`. + """ + return { + "output": "lCl mPk", + "l_max_scalars": lmax, + "lensing": "no", + "N_ncdm": 0, + "P_k_max_h/Mpc": kmax, + "h": x[0], + "Omega_b": x[1], + "Omega_m": x[2], + "n_s": x[3], + "sigma8": x[4], + } + + +def params_ids_to_Simbelmyne_dict(params_vals, params_ids, fixed, kmax): + """ + Convert a list of cosmological parameters into a dictionary + compatible with `pysbmy`. + + Fixed parameters remain unchanged unless overridden by + `params_vals`. + + Parameters + ---------- + params_vals : array-like + Values of the parameters to be modified. + params_ids : array-like + Indices of the parameters to be modified. + fixed : array-like + Base values of the parameters. + kmax : float + Maximum wavenumber for the power spectrum computation. + + Returns + ------- + dict + Dictionary of cosmological parameters compatible with `pysbmy`. + """ + from numpy import copy + + x = copy(fixed) + x[params_ids] = params_vals + return cosmo_vector_to_Simbelmyne_dict(x, kmax=kmax) + + +def get_summary(params_vals, params_ids, Omegas_fixed, bins, normalisation=None, kmax=1.4): + """ + Compute the normalised power spectrum summary for a given parameter + set. + + Parameters + ---------- + params_vals : array-like + Parameter values to update. + params_ids : array-like + Indices of the parameters to update. + Omegas_fixed : array-like + Fixed base values of parameters. + bins : array-like + Power spectrum bins. + normalisation : float, optional + Normalisation factor for the summary. + kmax : float, optional + Maximum wavenumber for power spectrum computation. + + Returns + ------- + array + Normalised power spectrum summary. + """ + from pysbmy.power import get_Pk + from numpy import array + + phi = get_Pk(bins, params_ids_to_Simbelmyne_dict(params_vals, params_ids, Omegas_fixed, kmax)) + + return array(phi) / normalisation if normalisation else array(phi) + + +def summary_to_score(params_ids, omega0, F0, F0_inv, f0, dw_f0, C0_inv, phi): + """ + Compute the Fisher score. + + Parameters + ---------- + params_ids : array-like + Indices of the parameters. + omega0 : array-like + Cosmological parameters at the expansion point. + F0 : array-like + Fisher information matrix. + F0_inv : array-like + Inverse Fisher information matrix. + f0 : array-like + Mean model at the expansion point. + dw_f0 : array-like + Derivative of the mean model. + C0_inv : array-like + Inverse covariance matrix. + phi : array-like + Observed summary. + + Returns + ------- + array + Fisher score. + """ + return omega0[params_ids] + F0_inv @ dw_f0.T @ C0_inv @ (phi - f0) + + +def fisher_rao(Com, Com_obs, F0): + """ + Compute the Fisher-Rao distance between two summaries. + + Parameters + ---------- + Com : array-like + Computed summary. + Com_obs : array-like + Observed summary. + F0 : array-like + Fisher information matrix. + + Returns + ------- + float + Fisher-Rao distance. + """ + from numpy import sqrt + + diff = Com - Com_obs + return sqrt(diff.T @ F0 @ diff) + + +def sample_omega_from_prior(nsample, omega_mean, omega_cov, params_ids, seed=None): + """ + Sample cosmological parameters from a prior distribution. + + Ensures physical validity by clipping values to [eps, 1-eps]. + + Parameters + ---------- + nsample : int + Number of samples to draw. + omega_mean : array-like + Prior mean vector. + omega_cov : array-like + Prior covariance matrix. + params_ids : array-like + Indices of the parameters to sample. + seed : int, optional + Seed for the random number generator. + + Returns + ------- + array + Sampled cosmological parameters. + """ + from numpy import array, ix_, clip + from numpy.random import default_rng + + if seed is None: + raise ValueError("A seed value is mandatory.") + + rng = default_rng(seed) + OO_unbounded = rng.multivariate_normal( + array(omega_mean)[params_ids], + array(omega_cov)[ix_(params_ids, params_ids)], + nsample, + ) + eps = 1e-5 + return clip(OO_unbounded, eps, 1 - eps) diff --git a/src/selfisys/utils/workers.py b/src/selfisys/utils/workers.py new file mode 100644 index 0000000..e532030 --- /dev/null +++ b/src/selfisys/utils/workers.py @@ -0,0 +1,385 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Routines for parameter inference and gradient evaluation in the SelfiSys +pipeline. +""" + +import gc +from typing import Any, Tuple, List + +from selfisys.utils.logger import getCustomLogger + +logger = getCustomLogger(__name__) + + +def Simbelmyne_worker(args) -> Tuple[float, Any]: + """ + Worker function used for implicit likelihood inference of + cosmological parameters. + + Parameters + ---------- + args : tuple + A tuple of arguments to be unpacked for the worker routine: + (index, param_val, param_id, fsimdir, k_s, Pbins_bnd, + selection_params, norm_csts, P_ss_obj_path, obs_density, + lin_bias, noise, survey_mask_path, G_sim_path, G_ss_path, Np0, + Npm0, seedphase_init, seednoise_init, size, L, + radial_selection, sim_params, wd, batch_idx, dbg, modeldir, + local_mask_prefix, TimeStepDistribution, indices_steps_cumul, + eff_redshifts, poolname_abc, setup_only, prefix_mocks). + + Returns + ------- + tuple + (param_val, Phi) where param_val is the parameter value used, + and Phi is the resulting summary from evaluating the model. + + Raises + ------ + OSError + If file I/O (reading or writing mock data) fails. + RuntimeError + For unexpected errors in the worker routine. + """ + import os + from pathlib import Path + + try: + ( + index, + param_val, + param_id, + fsimdir, + k_s, + Pbins_bnd, + selection_params, + norm_csts, + P_ss_obj_path, + obs_density, + lin_bias, + noise, + survey_mask_path, + G_sim_path, + G_ss_path, + Np0, + Npm0, + seedphase_init, + seednoise_init, + size, + L, + radial_selection, + sim_params, + wd, + batch_idx, + dbg, + modeldir, + local_mask_prefix, + TimeStepDistribution, + indices_steps_cumul, + eff_redshifts, + poolname_abc, + setup_only, + prefix_mocks, + ) = args + + spectrum_name = int(str(seedphase_init + index) + str(seednoise_init + index)) + pooldir = ( + fsimdir + "/pool/d" if not poolname_abc else fsimdir + "/pool/" + poolname_abc + "/d" + ) + simdir_d = pooldir + str(spectrum_name) + "/" + Path(simdir_d).mkdir(parents=True, exist_ok=True) + + if prefix_mocks is None: + fname_mocks = ( + simdir_d + "mocks_d" + str(spectrum_name) + "_p" + str(batch_idx + index) + ".h5" + ) + else: + fname_mocks = ( + simdir_d + + prefix_mocks + + "_mocks_d" + + str(spectrum_name) + + "_p" + + str(batch_idx + index) + + ".h5" + ) + + if os.path.exists(fname_mocks): + from h5py import File + + logger.debug("Mock file %s found, loading existing data...", fname_mocks) + with File(fname_mocks, "r") as f: + Phi = f["Phi"][:] + else: + logger.debug("No existing mock file at %s, generating new data...", fname_mocks) + from numpy.random import normal + from numpy import shape, max + from selfisys.global_parameters import BASELINE_SEEDNORM, omegas_gt + from selfisys.utils.tools import get_k_max + from selfisys.hiddenbox import HiddenBox + from selfisys.utils.tools import get_summary + + P = len(Pbins_bnd) - 1 + try: + BB_selfi = HiddenBox( + k_s=k_s, + P_ss_path=P_ss_obj_path, + Pbins_bnd=Pbins_bnd, + theta2P=None, + P=P * shape(selection_params)[1], # P * Npop + size=size, + L=L, + G_sim_path=G_sim_path, + G_ss_path=G_ss_path, + Np0=Np0, + Npm0=Npm0, + fsimdir=wd[:-1], + modeldir=modeldir, + noise_std=noise, + radial_selection=radial_selection, + selection_params=selection_params, + observed_density=obs_density, + linear_bias=lin_bias, + norm_csts=norm_csts, + survey_mask_path=survey_mask_path, + local_mask_prefix=local_mask_prefix, + sim_params=sim_params, + TimeStepDistribution=TimeStepDistribution, + TimeSteps=indices_steps_cumul, + eff_redshifts=eff_redshifts, + seedphase=seedphase_init, + seednoise=seednoise_init, + fixnoise=False, + seednorm=BASELINE_SEEDNORM, + reset=False, + save_frequency=5, + verbosity=2, + ) + k_max = get_k_max(L, size) + except Exception as e: + logger.critical("Error instantiating HiddenBox: %s", str(e)) + raise RuntimeError("Failed to set up HiddenBox.") from e + + # Evaluate the param -> 'theta' using some get_summary logic + try: + theta = get_summary(param_val, param_id, omegas_gt, k_s, kmax=k_max) + except Exception: + max_tries = 10 + perturb_std = 1e-8 + param_val_init = param_val + logger.warning( + "get_summary failed for param_val=%s. Trying small perturbations...", param_val + ) + for i in range(max_tries): + param_val = normal(param_val_init, perturb_std) + logger.diagnostic("Attempt #%d: param_val=%s", i + 1, param_val) + try: + theta = get_summary(param_val, param_id, omegas_gt, k_s, kmax=k_max) + logger.diagnostic( + "Success with param_val=%s on attempt #%d", param_val, i + 1 + ) + break + except Exception: + if i == max_tries - 1: + logger.critical( + "All attempts to get_summary failed for param_val=%s", + param_val_init, + ) + raise RuntimeError("get_summary repeatedly failed.") + continue + + from io import BytesIO + from selfisys.utils.low_level import stderr_redirector, stdout_redirector + + cosmo_vect = omegas_gt + cosmo_vect[param_id] = param_val + + logger.debug("Evaluating model with HPC redirection, setup_only=%s", setup_only) + f = BytesIO() + g = BytesIO() + try: + with stderr_redirector(f): + with stdout_redirector(g): + if setup_only: + BB_selfi.switch_setup() + else: + BB_selfi.switch_recompute_pool(prefix_mocks=prefix_mocks) + + Phi = BB_selfi.evaluate( + theta, + spectrum_name, + seedphase_init + index, + seednoise_init + index, + i=batch_idx + index, + thetaIsP=True, + remove_sbmy=True, + force_powerspectrum=dbg, + force_parfiles=dbg, + check_output=dbg, + abc=poolname_abc, + cosmo_vect=cosmo_vect, + ) + if setup_only: + BB_selfi.switch_setup() + else: + BB_selfi.switch_recompute_pool(prefix_mocks=prefix_mocks) + + except Exception as e: + logger.critical("Error while evaluating model: %s", str(e)) + raise RuntimeError("Simbelmyne_worker model evaluation failed.") from e + finally: + g.close() + f.close() + + logger.debug("Returning param_val=%s with resulting Phi of shape %s", param_val, Phi.shape) + return param_val, Phi + + except OSError as e: + logger.error("File I/O error in Simbelmyne_worker: %s", str(e)) + raise + except Exception as e: + logger.critical("Unexpected error in Simbelmyne_worker: %s", str(e)) + raise RuntimeError("Simbelmyne_worker HPC run failed.") from e + finally: + gc.collect() + + +def worker_gradient_Symbelmyne( + coeff: float, + delta_x: float, + omega, + param_index: int, + k_s, + delta: float, + kmax: float, +): + """ + Worker function for evaluating the gradient of the power spectrum + using finite differences. + + Parameters + ---------- + coeff : float + Coefficient for the finite difference. + delta_x : float + Step size in the parameter space. + omega : ndarray + Base cosmological parameter vector. + param_index : int + Index of the parameter being varied. + k_s : ndarray + Array of wavenumbers. + delta : float + Denominator for finite differences (scaled). + kmax : float + Maximum wavenumber for power spectrum. + + Returns + ------- + ndarray + The gradient of the power spectrum wrt the specified parameter. + + Raises + ------ + RuntimeError + If the gradient evaluation fails. + """ + import numpy as np + from pysbmy.power import get_Pk + from selfisys.utils.tools import cosmo_vector_to_Simbelmyne_dict + + omega_new = omega.copy() + try: + omega_new[param_index] += delta_x + ps = get_Pk(k_s, cosmo_vector_to_Simbelmyne_dict(omega_new, kmax=kmax)) + contrib_to_grad = (coeff * ps) / delta + return np.array(contrib_to_grad) + except Exception as e: + logger.critical("Error in worker_gradient_Symbelmyne: %s", str(e)) + raise RuntimeError("worker_gradient_Symbelmyne failed.") from e + finally: + gc.collect() + + +def evaluate_gradient_of_Symbelmyne( + omega, + param_index: int, + k_s, + coeffs: List[float] = [2 / 3.0, -1 / 12.0], + deltas_x: List[float] = [0.01, 0.02], + delta: float = 1e-2, + kmax: float = 1.4, +): + """ + Estimate the gradient of CLASS with respect to the cosmological + parameters using central finite differences of arbitrary order. + + Parameters + ---------- + omega : ndarray + Base cosmological parameter vector. + param_index : int + Index of the parameter to differentiate against. + k_s : ndarray + Wavenumbers for the power spectrum. + coeffs : list of float, optional + Coefficients for the finite-difference scheme, typically + [2/3, -1/12] etc. Default is [2/3.0, -1/12.0]. + deltas_x : list of float, optional + Step sizes. The corresponding negative steps are generated + automatically. Default is [0.01, 0.02]. + delta : float, optional + Scale for the finite difference in the denominator. Default is + 1e-2. + kmax : float, optional + Maximum wavenumber for the power spectrum. Default is 1.4. + + Returns + ------- + ndarray + The gradient of the power spectrum wrt the specified parameter. + + Raises + ------ + RuntimeError + If the gradient evaluation fails. + """ + import numpy as np + from multiprocessing import Pool + + try: + grad = np.zeros(len(k_s)) + full_coeffs = np.concatenate((-np.array(coeffs)[::-1], coeffs)) + deltas_x_full = np.concatenate((-np.array(deltas_x)[::-1], deltas_x)) + + tasks = [ + (c, dx, omega, param_index, k_s, delta, kmax) + for c, dx in zip(full_coeffs, deltas_x_full) + ] + logger.diagnostic("Starting parallel HPC for gradient, tasks=%d", len(tasks)) + + with Pool() as mp_pool: + results = mp_pool.starmap(worker_gradient_Symbelmyne, tasks) + for contrib in results: + grad += contrib + + logger.diagnostic("Gradient evaluation completed. Shape=%s", grad.shape) + return grad + except Exception as e: + logger.critical("Unexpected error in evaluate_gradient_of_Symbelmyne: %s", str(e)) + raise RuntimeError("evaluate_gradient_of_Symbelmyne failed.") from e + finally: + gc.collect() diff --git a/src/setup.py b/src/setup.py new file mode 100644 index 0000000..b168726 --- /dev/null +++ b/src/setup.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +__author__ = "Tristan Hoellinger" +__version__ = "0.1.0" +__date__ = "2024" +__license__ = "GPLv3" + +""" +Setup script for the SelfiSys package. + +SelfiSys enables thorough diagnosis of systematic effects in +field-based, implicit likelihood inference (ILI) of cosmological +parameters from large-scale spectroscopic galaxy surveys. +""" + +from setuptools import setup, find_packages +import os + +# Read the long description from README.md +here = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(here, "../README.md"), encoding="utf-8") as f: + long_description = f.read() + +setup( + name="selfisys", + version="0.1.0", + author="Tristan Hoellinger", + author_email="tristan.hoellinger@iap.fr", + description="Diagnosing systematic effects in implicit likelihood cosmological inferences.", + long_description=long_description, + long_description_content_type="text/markdown", + packages=find_packages(), + include_package_data=True, + url="https://github.com/hoellin/selfisys_public", + package_data={"selfisys": ["preamble.tex"]}, + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Programming Language :: Python :: 3", + "Topic :: Scientific/Engineering :: Astronomy", + ], + python_requires=">=3.7", + license="GPLv3", + keywords="cosmology systematic-effects large-scale-structure systematics implicit-likelihood-inference misspecification robust-inference galaxy-surveys", +) diff --git a/submit/step0a.sh b/submit/step0a.sh new file mode 100644 index 0000000..0952387 --- /dev/null +++ b/submit/step0a.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +# Author: Tristan Hoellinger +# Version: 0.1 +# Date: 2024 +# License: GPLv3 + +eval "$(conda shell.bash hook)" +conda activate simbel + + +export OMP_NUM_THREADS=8 +python $SELFISYS_ROOT_PATH"src/selfisys/pipeline/step0a.py" \ + --wd_ext dev/ \ + --name run1 \ + --size 64 \ + --Np0 64 \ + --Npm0 64 \ + --L 3600 \ + --S 32 \ + --Pinit 50 \ + --Nnorm 2 \ + --total_steps 10 \ + --sim_params "custom19COLA20" \ + --aa 0.05 0.1 0.4 1 \ + --Ne 150 \ + --Ns 3 \ + --OUTDIR $SELFISYS_OUTPUT_PATH \ + --radial_selection "multiple_lognormal" \ + --selection_params 0.1150 0.1492 0.1818 0.1500 0.4925 0.8182 1 1 1 \ + --survey_mask_path $SELFISYS_OUTPUT_PATH"expl_notebooks/surveymask/raw_mask_N64.npy" \ + --lin_bias 1.47 1.99 2.32 \ + --prior "planck2018" \ + --nsamples_prior 10000 \ + --noise 0.1 \ + --force True + +exit 0 diff --git a/submit/step0b.sh b/submit/step0b.sh new file mode 100644 index 0000000..602cc9c --- /dev/null +++ b/submit/step0b.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +# Author: Tristan Hoellinger +# Version: 0.1 +# Date: 2024 +# License: GPLv3 + +eval "$(conda shell.bash hook)" +conda activate simbel + +ii_array=-1 +# ii_array=( $(seq 0 1 ) ) +# ii_array=( $(seq 0 9 ) ) + +export OMP_NUM_THREADS=8 +python $SELFISYS_ROOT_PATH"src/selfisys/pipeline/step0b.py" \ + --pool_path $SELFISYS_OUTPUT_PATH"dev/643600502/run1/data/" \ + --ii ${ii_array[@]} \ + --npar 1 \ + --force False + +exit 0 \ No newline at end of file diff --git a/submit/step0c.sh b/submit/step0c.sh new file mode 100644 index 0000000..747baab --- /dev/null +++ b/submit/step0c.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +# Author: Tristan Hoellinger +# Version: 0.1 +# Date: 2024 +# License: GPLv3 + +eval "$(conda shell.bash hook)" +conda activate simbel + + +export OMP_NUM_THREADS=8 +python $SELFISYS_ROOT_PATH"src/selfisys/pipeline/step0c.py" \ + --wd $SELFISYS_OUTPUT_PATH"dev/643600502/run1/" \ + --npar_norm 1 \ + --survey_mask_path $SELFISYS_OUTPUT_PATH"expl_notebooks/surveymask/raw_mask_N64.npy" \ + --force False + +exit 0 diff --git a/submit/step0d.sh b/submit/step0d.sh new file mode 100644 index 0000000..8b6b1d6 --- /dev/null +++ b/submit/step0d.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +# Author: Tristan Hoellinger +# Version: 0.1 +# Date: 2024 +# License: GPLv3 + +eval "$(conda shell.bash hook)" +conda activate simbel + +export OMP_NUM_THREADS=8 +python $SELFISYS_ROOT_PATH"src/selfisys/pipeline/step0d.py" \ + --wd $SELFISYS_OUTPUT_PATH"dev/643600502/run1/" \ + --survey_mask_path $SELFISYS_OUTPUT_PATH"expl_notebooks/surveymask/raw_mask_N64.npy" \ + --name_obs obs \ + --reset_window_function True \ + --force_obs True + +exit 0 diff --git a/submit/step0e.sh b/submit/step0e.sh new file mode 100644 index 0000000..1db3442 --- /dev/null +++ b/submit/step0e.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +# Author: Tristan Hoellinger +# Version: 0.1 +# Date: 2024 +# License: GPLv3 + +eval "$(conda shell.bash hook)" +conda activate simbel + +export OMP_NUM_THREADS=1 +python $SELFISYS_ROOT_PATH"src/selfisys/pipeline/step0e.py" \ + --wd $SELFISYS_OUTPUT_PATH"dev/643600502/run1/" \ + --N_THREADS 8 \ + --prior "planck2018" \ + --nsamples_prior 100 \ + --survey_mask_path $SELFISYS_OUTPUT_PATH"expl_notebooks/surveymask/raw_mask_N64.npy" \ + --name_obs obs \ + --force_recompute_prior False + +exit 0 diff --git a/submit/step1.sh b/submit/step1.sh new file mode 100644 index 0000000..1ba9e24 --- /dev/null +++ b/submit/step1.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +# Author: Tristan Hoellinger +# Version: 0.1 +# Date: 2024 +# License: GPLv3 + +eval "$(conda shell.bash hook)" +conda activate simbel + +# Run the Simbelmynë simulations at expansion point (all sims if +# pp_array=-1, selected sims otherwise). + +dir_array=0 + +pp_array=-1 +# pp_array=( $(seq 0 50 ) ) +# pp_array=( $(seq 495 499 ) ) + +export OMP_NUM_THREADS=1 # number of threads per process e.g. number of + # threads per Simbelmynë simulation +python $SELFISYS_ROOT_PATH"src/selfisys/pipeline/step1_2.py" \ + --pool_path $SELFISYS_OUTPUT_PATH"dev/643600502/run1/pool/" \ + --directions ${dir_array[@]} \ + --pp ${pp_array[@]} \ + --Npop 3 \ + --npar 8 \ + --force True + +exit 0 diff --git a/submit/step2.sh b/submit/step2.sh new file mode 100644 index 0000000..f7803a4 --- /dev/null +++ b/submit/step2.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +# Author: Tristan Hoellinger +# Version: 0.1 +# Date: 2024 +# License: GPLv3 + +eval "$(conda shell.bash hook)" +conda activate simbel + +# Run the Simbelmynë simulations in the directions dir_array of the +# parameter space, (all sims if pp_array=-1, selected sims otherwise). + +dir_array=( $(seq 1 32 ) ) +# dir_array=( $(seq 62 64 ) ) +# dir_array=( $(seq 1 64 ) ) + +pp_array=-1 +# pp_array=( $(seq 0 1 ) ) +# pp_array=( $(seq 0 9 ) ) + +export OMP_NUM_THREADS=1 +python $SELFISYS_ROOT_PATH"src/selfisys/pipeline/step1_2.py" \ + --pool_path $SELFISYS_OUTPUT_PATH"dev/643600502/run1/pool/" \ + --directions ${dir_array[@]} \ + --pp ${pp_array[@]} \ + --Npop 3 \ + --npar 8 \ + --force True + +exit 0 \ No newline at end of file diff --git a/submit/step3.sh b/submit/step3.sh new file mode 100644 index 0000000..a183dff --- /dev/null +++ b/submit/step3.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# ---------------------------------------------------------------------- +# Copyright (C) 2024 Tristan Hoellinger +# Distributed under the GNU General Public License v3.0 (GPLv3). +# See the LICENSE file in the root directory for details. +# SPDX-License-Identifier: GPL-3.0-or-later +# ---------------------------------------------------------------------- + +# Author: Tristan Hoellinger +# Version: 0.1 +# Date: 2024 +# License: GPLv3 + +eval "$(conda shell.bash hook)" +conda activate simbel + +# Notes: +# - One shall use "recompute_mocks True" when running step3 for the +# first time within a given run. +# - Afterwards, one can, again, use "recompute_mocks True" for +# recomputing the mocks with a misspecified model. + +export OMP_NUM_THREADS=1 +python $SELFISYS_ROOT_PATH"src/selfisys/pipeline/step3.py" \ + --wd $SELFISYS_OUTPUT_PATH"dev/643600502/run1/" \ + --N_THREADS 8 \ + --N_THREADS_PRIOR 8 \ + --prior "planck2018" \ + --nsamples_prior 1000 \ + --survey_mask_path $SELFISYS_OUTPUT_PATH"expl_notebooks/surveymask/raw_mask_N64.npy" \ + --params_obs None \ + --name_obs obs \ + --force_obs False \ + --recompute_obs_mock False \ + --reset_window_function True \ + --selection_params 0.1150 0.1492 0.1818 0.1500 0.4925 0.8182 1 1 1 \ + --Ne 150 \ + --Ns 3 \ + --prefix_mocks None \ + --force_recompute_prior False \ + --lin_bias 1.47 1.99 2.32 \ + --recompute_mocks True \ + --perform_score_compression True \ + --force_score_compression False + + # --lin_bias 1.47 1.99 2.32 \ + # --recompute_mocks True \ + + # --noise_dbg 0.1 \ + +exit 0