diff --git a/tracks/mps/solutions/Ranger-123/.gitignore b/tracks/mps/solutions/Ranger-123/.gitignore new file mode 100644 index 000000000..94ec4eaa6 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/.gitignore @@ -0,0 +1,15 @@ +.venv*/ +__pycache__/ +*.py[cod] +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.omx/ +work/ +build/ +dist/ +*.egg-info/ +/results/ +/docs/plans/ +/docs/superpowers/ +/julia/Manifest.toml diff --git a/tracks/mps/solutions/Ranger-123/CITATION.cff b/tracks/mps/solutions/Ranger-123/CITATION.cff new file mode 100644 index 000000000..7786cfbfb --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/CITATION.cff @@ -0,0 +1,29 @@ +cff-version: 1.2.0 +title: "Floquet-IF Many-body: N=2,3" +message: "If you use this research implementation, cite this software and the upstream methods." +type: software +authors: + - family-names: "Wang" + given-names: "Thomas" +version: 0.1.0 +date-released: 2026-07-29 +license: AGPL-3.0-or-later +references: + - type: article + title: "Exact Floquet Dynamics of Strongly Damped Driven Quantum Systems" + authors: + - family-names: Mickiewicz + given-names: Konrad + - family-names: Link + given-names: Valentin + - family-names: Strunz + given-names: Walter T. + year: 2026 + doi: 10.1103/5z1m-122d + - type: software + title: "UniformTEMPO.jl" + url: "https://github.com/uniformTEMPO/UniformTEMPO.jl" + version: "b76a018c32e5415989761d902b1b0e95f1a337da" + - type: software + title: "OQuPy" + url: "https://github.com/tempoCollaboration/OQuPy" diff --git a/tracks/mps/solutions/Ranger-123/LICENSE b/tracks/mps/solutions/Ranger-123/LICENSE new file mode 100644 index 000000000..0ad25db4b --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/tracks/mps/solutions/Ranger-123/README.md b/tracks/mps/solutions/Ranger-123/README.md new file mode 100644 index 000000000..458ebf1e2 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/README.md @@ -0,0 +1,146 @@ +# Floquet-IF Many-body: N=1--4 validation and N=2,3 production + +## Submission + +- Team: Ranger +- Members: Chenxi Wan, Yedi Shen, Junkai Wang +- Challenge: #123 +- Track: MPS +- Completed scope: published single-spin validation, reproducible collective + common-bath calculations for \(N=2,3\), a convergence-gated \(N=4\) point, + and quantitative non-Markovian--Floquet-Markov benchmarks. + +This repository is a reproducible implementation of the two- and three-spin +part of [QuantumBFS/quantum.harness issue #123](https://github.com/QuantumBFS/quantum.harness/issues/123). +It combines exact symmetry reduction, a public uniform influence-functional +solver, frequency-resolved bath heat currents, and a Floquet-Markov benchmark. + +The production backend is +[UniformTEMPO.jl](https://github.com/uniformTEMPO/UniformTEMPO.jl), pinned to +revision `b76a018c32e5415989761d902b1b0e95f1a337da`. The Python layer projects +the physical model into its symmetry sectors, runs Julia, caches the uniform +process tensor atomically, inserts operators into the extended process-tensor +state, separates coherent delta peaks from the decaying correlation, and +records every convergence comparison in JSON. + +## Completed results + +- Exact \(N=2\) singlet/triplet reduction, dark singlet, bright gaps, and + transition weights. +- Exact \(N=3\) reflection \(6\oplus2\) reduction and the + \(J\)-independent embedded single-spin odd sector. +- A converged six-point \(N=3\) heat-spectrum grid for + \(J/\Omega=0.25,0.5,1\) in both reflection sectors. +- A converged \(3\times3\) \(N=2\) calibration grid comparing uniform TEMPO + with Floquet-Markov/QRT using state, correlation, and heat-spectrum errors. +- A converged 6/6 same-model \(N=3\) benchmark comparing each existing + reflection-sector UniformTEMPO point directly to Floquet-Markov/QRT. +- Floquet matrix-element, collective-variance, counterterm, and normalization + diagnostics. +- Full convergence of the two bounded-normalization model variants. +- Compression convergence of both Kac-normalized variants, with their + remaining timestep/phase refinement explicitly marked as cluster work. +- An independent 3/3 reproduction of the published transversal-drive Fig. 3 + bottom panel, plus a coarse UniformTEMPO--OQuPy cross-check. +- A converged reflection-odd \(N=4\) pilot with a same-model + Floquet-Markov/QRT heat-spectrum error of 3.405 and resolved collective + spectral peaks. +- A converged reflection-even \(N=4\) pilot using an automatically extended + six-period correlation window; its same-model heat-spectrum error is 5.494. +- A pole-resolved, fixed-frequency \(N=1,2,3\) heat-valve pre-scan and + three-point \(N=3\) UniformTEMPO go/no-go pilot. The pilot rejects—not + confirms—the dark-channel hypothesis: the quasienergy gap collapses while + the observable transfer-pole residue increases. + +The main \(N=2\) and \(N=3\) results do **not** require a cluster. The failed +heat-valve pilot does not justify a cluster-scale nine-point continuation. +Only the optional full Kac refinement does. Reflection-resolved \(N=4\) +support is implemented and both sector pilots pass all declared gates; the +even sector records the required six-period tail-window extension explicitly. +The project makes no thermodynamic-limit, continuum, or critical-exponent +claim. + +## Reproduce + +Python 3.12 and Julia 1.12 are the recorded production versions. + +```bash +python3.12 -m venv .venv +.venv/bin/python -m pip install -e '.[dev,nonmarkov]' +julia --project=julia -e 'using Pkg; Pkg.instantiate()' + +# Exact and legacy validation baselines +PYTHON_BIN=.venv/bin/python scripts/run_baselines.sh +PYTHON_BIN=.venv/bin/python scripts/run_pt_baselines.sh + +# Resumable production calculation and strict audit +PYTHON_BIN=.venv/bin/python scripts/run_paper_extension.sh all + +# Independent backend checks +.venv/bin/python scripts/run_uniform_validation.py +.venv/bin/python scripts/run_fig3_validation.py --drive-frequency 1 +.venv/bin/python scripts/run_fig3_validation.py --drive-frequency 1.5 +.venv/bin/python scripts/run_fig3_validation.py --drive-frequency 2 +.venv/bin/python scripts/run_fig3_validation.py --plot-summary + +# Convergence-gated N=4 sector points +.venv/bin/python -m floquet_if_manybody.cli n4-pilot --sector odd --j 0.25 +.venv/bin/python -m floquet_if_manybody.cli n4-pilot --sector even --j 0.25 + +# Software and result verification +.venv/bin/python -m pytest -q +.venv/bin/python -m ruff check src tests scripts/run_uniform_validation.py scripts/run_fig3_validation.py +.venv/bin/python -m mypy src scripts/run_fig3_validation.py +.venv/bin/python -m floquet_if_manybody.cli audit results +.venv/bin/python -m floquet_if_manybody.cli paper-audit results/paper + +# Pole-resolved heat-valve pilot. The audit intentionally exits nonzero +# because the dark-channel claim gates are not met. +.venv/bin/python -m floquet_if_manybody.cli heat-valve --pilot +.venv/bin/python -m floquet_if_manybody.cli \ + heat-valve-audit results/heat-valve +``` + +The production command is resumable. Its cache is content-addressed by the +physical model, numerical controls, projected operators, solver revision, and +source revision. Cache files are excluded from the research archive. + +The upstream harness excludes machine-generated `results/` data from Git. +The commands above reproduce those local files. This submission commits the +publication figures, reports, test suite, and compact validation snapshots; +`validation/ARTIFACT_PROVENANCE.json` records hashes for the audited local +artifact set. + +To continue the Kac variants on a cluster: + +```bash +FULL_KAC=1 PYTHON_BIN=.venv/bin/python \ + scripts/run_paper_extension.sh models +``` + +## Result guide + +| Artifact | Meaning | Status | +|---|---|---| +| `figures/n2_exact.*` | Interacting-triplet gaps and bright weights | Exact | +| `figures/n3_exact.*` | Collective cat gap and weight | Exact | +| `figures/paper/n3_sector_heat.*` | \(N=3\) even/odd heat spectra | 6/6 converged | +| `figures/paper/n3_odd_difference.*` | Odd-sector \(J\)-invariance residual | Exact zero on the projected grid | +| `figures/paper/error_maps.*` | Uniform TEMPO vs Floquet-Markov/QRT | 9/9 converged | +| `figures/paper/n3_error_maps.*` | Same-parameter \(N=3\) UniformTEMPO vs Floquet-Markov/QRT | 6/6 converged | +| `figures/validation/fig3_transversal_summary.*` | Published Fig. 3 bottom data vs independent calculation | 3/3 physical gates passed | +| `figures/paper/n4_odd_j0p25_comparison.*` | Same-model \(N=4\) odd-sector comparison | Converged; heat error 3.405 | +| `figures/paper/n4_even_j0p25_comparison.*` | Same-model \(N=4\) even-sector comparison | Converged; heat error 5.494 | +| `figures/paper/dark_diagnostics.*` | Floquet matrix elements, heat, and \(\mathrm{Var}(S)\) | Converged heat plus exact Floquet diagnostic | +| `figures/paper/model_variants.*` | Bounded/Kac and counterterm comparison | Bounded converged; Kac compression-audited | +| `figures/heat-valve/heat_valve_hero.*` | Quasienergy collapse tested against exact transfer-pole residues | Pilot complete; dark-channel claim rejected | +| `docs/heat-valve-result-zh.md` | Fixed-frequency go/no-go evidence and independent claim audit | 3/3 \(N=3\) pilot points; full grid intentionally not run | +| `validation/*.json` | Artifact provenance and independent backend checks | Diagnostic validation | + +See [the Chinese report](docs/report-zh.md), +[the heat-valve pilot result](docs/heat-valve-result-zh.md), +[theory conventions](docs/theory.md), [numerical methods](docs/methods.md), and +the [completion matrix](docs/completion-matrix.md). + +This integrated project is distributed under the repository's +AGPL-3.0-or-later license. Third-party solvers retain their own licenses. diff --git a/tracks/mps/solutions/Ranger-123/configs/n2_baseline.yaml b/tracks/mps/solutions/Ranger-123/configs/n2_baseline.yaml new file mode 100644 index 000000000..bd55efec0 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/configs/n2_baseline.yaml @@ -0,0 +1,13 @@ +model: + n: 2 + j: 0.5 + omega: 1.0 + drive_amplitude: 0.2 + drive_frequency: 0.6180339887498949 + normalization: bounded +bath: + alpha: 0.05 + cutoff: 2.5 + temperature: 0.0 +sector: triplet +method: finite_memory_if diff --git a/tracks/mps/solutions/Ranger-123/configs/n3_even_baseline.yaml b/tracks/mps/solutions/Ranger-123/configs/n3_even_baseline.yaml new file mode 100644 index 000000000..c6aadd299 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/configs/n3_even_baseline.yaml @@ -0,0 +1,13 @@ +model: + n: 3 + j: 0.5 + omega: 1.0 + drive_amplitude: 0.2 + drive_frequency: 0.4450418679126287 + normalization: bounded +bath: + alpha: 0.05 + cutoff: 2.5 + temperature: 0.0 +sector: even +method: floquet_markov diff --git a/tracks/mps/solutions/Ranger-123/configs/n3_odd_check.yaml b/tracks/mps/solutions/Ranger-123/configs/n3_odd_check.yaml new file mode 100644 index 000000000..bf9cbefb1 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/configs/n3_odd_check.yaml @@ -0,0 +1,9 @@ +model: + n: 3 + j: 1.0 + omega: 1.0 + drive_amplitude: 0.2 + drive_frequency: 1.0 + normalization: bounded +sector: odd +method: closed diff --git a/tracks/mps/solutions/Ranger-123/docs/completion-matrix.md b/tracks/mps/solutions/Ranger-123/docs/completion-matrix.md new file mode 100644 index 000000000..e94fc7dee --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/docs/completion-matrix.md @@ -0,0 +1,70 @@ +# Completion matrix + +| Requirement | Evidence | Status | +|---|---|---| +| Reproducible Python/Julia package | `pyproject.toml`, `uv.lock`, pinned source in `julia/Project.toml` | Complete | +| \(N=2\) singlet dark sector | `tests/test_symmetry_n2.py` | Complete | +| \(N=2\) exact gaps and weights | `tests/test_spectra_n2.py`, `figures/n2_exact.*` | Complete | +| \(N=3\) \(6\oplus2\) reflection split | `tests/test_symmetry_n3.py` | Complete | +| \(N=3\) odd-sector \(J\)-independence | projected operator hashes and zero curve difference | Complete | +| \(N=3\) cat-gap coefficient | `tests/test_spectra_n3.py` | Complete | +| Closed Floquet solver | `tests/test_floquet.py` | Complete | +| Public uniform process-tensor backend | pinned UniformTEMPO revision, Julia runner | Complete | +| Extended-state multi-time insertions | Julia runner and backend tests | Complete | +| Delta/continuum separation | correlation and heat-current tests | Complete | +| Atomic, content-addressed cache | convergence/backend tests | Complete | +| Nested compression/timestep/phase controller | adaptive tests and per-point evidence | Complete | +| \(N=3\) even/odd heat grid | `figures/paper/n3_sector_heat.*`, audited local manifest | 6/6 converged | +| Odd-sector spectral invariance | relative maximum difference 0 | Complete | +| \(N=2\) exact-vs-Markov grid | `error_map_manifest.json` | 9/9 converged | +| Three Markov-error metrics | all nine cells | Complete | +| \(N=3\) same-model exact-vs-Markov grid | `validation/n3_same_model_error_map.json`, `figures/paper/n3_error_maps.*` | 6/6 converged | +| Floquet dark-channel diagnostics | \(|m|\le40\), Parseval tests | Complete diagnostic | +| Independent drive/bath normalization | model and projected-sector regression tests | Complete | +| Floquet transfer eigenvalues and residuals | Julia Krylov extraction plus real \(N=1\) smoke | Complete | +| Observable pole-residue fit and mode tracking | synthetic exact-recovery and matching tests | Complete | +| Fixed-frequency \(N=1,2,3\) coherent-destruction scan | `docs/heat-valve-result-zh.md`, hero figure | Complete | +| \(N=3\) pole-resolved heat-valve pilot | report, hero figure, and audited local manifest | 3/3 executed; claim gates rejected | +| Nine-point \(N=1,2,3\) heat-valve grid | go/no-go required threefold heat and residue suppression | Intentionally not run: pilot failed | +| Bounded normalization/counterterm variants | full adaptive evidence | 2/2 converged | +| Kac normalization/counterterm variants | passed compression evidence | Local endpoint complete; full timestep/phase requires cluster | +| Independent single-spin validation | `validation/uniform_tempo_single_spin.json` | Passed | +| Published Fig. 3 bottom-panel reproduction | `validation/fig3_transversal_summary.json`, `figures/validation/fig3_transversal_summary.*` | 3/3 physical gates passed | +| Independent OQuPy cross-check | `validation/uniform_tempo_oqupy_crosscheck.json` | Diagnostic, not convergence | +| Publication PNG/PDF figures | `figures/paper` | Visually checked | +| Pole-resolved hero figure | `figures/heat-valve/heat_valve_hero.{png,pdf}` | Visually checked; labeled candidate/rejected | +| Chinese report | `docs/report-zh.md` | Complete | +| Heat-valve negative-result report | `docs/heat-valve-result-zh.md` | Complete | +| \(N=4\) reflection implementation | `tests/test_symmetry_n4.py`, convergence-gated `n4-pilot` command | Complete, \(10\oplus6\) blocks | +| \(N=4\) reflection-odd pilot and same-model benchmark | `figures/paper/n4_odd_j0p25_comparison.*` | Converged; \(\epsilon_j=3.405\) | +| \(N=4\) reflection-even pilot and same-model benchmark | six-period tail-window evidence, comparison figure | Converged; \(\epsilon_j=5.494\) | +| Continuum and critical exponent | Outside finite-system scope | Not claimed | + +## Final gates + +```bash +.venv/bin/python -m pytest -q +.venv/bin/ruff check src tests scripts/run_uniform_validation.py +.venv/bin/mypy src +.venv/bin/python -m floquet_if_manybody.cli audit results +.venv/bin/python -m floquet_if_manybody.cli paper-audit results/paper +``` + +`paper-audit` requires all six \(N=3\) points and all nine error-grid points to +be converged. It also requires both bounded model variants to be fully +converged. The two Kac variants are accepted only as an explicitly declared +timestep resource ceiling after a passed compression comparison; they are +never re-labeled as converged. + +The additional command + +```bash +.venv/bin/python -m floquet_if_manybody.cli \ + heat-valve-audit results/heat-valve +``` + +is expected to return nonzero for the archived pilot. This is a scientific +result, not a software failure: heat is not suppressed against both flanks, +the visible pole residue increases, the full nine-point grid was therefore +not authorized by the predeclared resource gate, and the figure is labeled +`candidate; claim gates not met`. diff --git a/tracks/mps/solutions/Ranger-123/docs/heat-valve-result-zh.md b/tracks/mps/solutions/Ranger-123/docs/heat-valve-result-zh.md new file mode 100644 index 000000000..1580a332d --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/docs/heat-valve-result-zh.md @@ -0,0 +1,207 @@ +# Floquet pole heat-valve pilot:准能隙塌缩不等于暗通道 + +## 结论 + +本轮新增实验完成了一个固定频率、固定浴、可由 transfer poles 独立审计的 +\(N=1,2,3\) Floquet heat-valve 搜索。结果是否定性的: + +\[ +\boxed{ +\text{闭系统 quasienergy/cat gap 的深极小} +\;\not\Rightarrow\; +\text{开放系统 heat 或 observable residue 变暗} +} +\] + +在 \(N=3\) 的闭系统扫描中, + +\[ +\omega_d=3\Omega,\qquad J=\Omega,\qquad +\xi=\frac{2A}{\omega_d}, +\] + +于 \(\xi=3.05\) 得到 + +\[ +\Delta_{\rm cat}=5.85\times10^{-5}\Omega. +\] + +但是同一点的 UniformTEMPO 结果显示: + +- integrated absolute continuous heat 只从左侧的 0.009806 降到 + 0.008976,之后在右侧继续降到 0.008747; +- visible transfer-pole residue weight 从 0.4547 增至 0.9812,并继续 + 增至 1.5278; +- dominant residue 同样从 0.2411 增至 0.5370,再增至 0.8276。 + +所以 \(\xi=3.05\) 既不是双侧 heat minimum,也不是 residue minimum。 +独立审计器正确拒绝 `dark channel` 文字。根据预先规定的 go/no-go gate, +没有扩展到九个 \(N=1,2,3\) UniformTEMPO 点,也没有提交集群。 + +![Pole-resolved heat-valve pilot](../figures/heat-valve/heat_valve_hero.png) + +## 1. 为什么要把 drive 和 bath normalization 分开 + +旧基准使用同一算符同时表示纵向驱动和共同浴耦合: + +\[ +S_N=\frac1N M_z. +\] + +这会让“相同 drive amplitude”实际对应不同的每自旋驱动力。新实验明确 +使用 + +\[ +H_{\rm drive}(t)=A\cos(\omega_dt)M_z, +\qquad +H_{SB}=\frac{M_z}{N}\otimes B. +\] + +因此不同 \(N\) 之间保持物理驱动 \(A\) 相同,同时继续使用 bounded bath +coupling。代码默认仍为旧的 `drive_normalization="coupling"`,只有本实验 +选择 `per_spin`,所以历史结果语义没有改变。 + +## 2. 预先规定的判据 + +只有同时满足以下条件,结果才允许称为 dark channel: + +1. \(\omega_d,J,\alpha,\omega_c,T_B\) 和 bath normalization 在扫描中固定; +2. minimum 相对左右两个 flank 的 continuous heat 均至少降低十倍; +3. 相应 observable transfer-pole residue 相对两个 flank 均至少降低十倍; +4. transfer eigenpair residual 不超过 \(10^{-8}\); +5. 所有物理 poles 满足 \(|\lambda|\le1+10^{-6}\); +6. pole reconstruction 的 normalized \(L^1\) residual 不超过 5%; +7. trace、Hermiticity、fixed point 和 connected tail 通过物理门槛。 + +在 pilot 阶段,只有 heat 和 residue 都至少降低三倍,才值得分配资源给完整 +九点网格。这个 pilot gate 远未满足。 + +## 3. 闭系统预扫描 + +预扫描固定 + +\[ +\Omega=1,\quad J=1,\quad\omega_d=3,\quad +\xi\in[1.8,4.0], +\] + +采用步长 0.05。每个点用 240 个 midpoint Floquet steps。\(N=2\) 投影到 +triplet,\(N=3\) 投影到 reflection-even 六维 sector。 + +| \(N\) | fitted \(\xi_\ast\) | cat gap | cat-subspace overlap | \(M_z/N\) brightness | +|---:|---:|---:|---:|---:| +| 1 | 2.35 | \(1.1402\times10^{-2}\) | 1.0000 | 1.8813 | +| 2 | 3.65 | \(6.5304\times10^{-2}\) | 0.9378 | 1.8800 | +| 3 | 3.05 | \(5.8516\times10^{-5}\) | 0.8638 | 1.7279 | + +\(N=1\) minimum 靠近第一 Bessel 零点 \(x_1=2.4048256\)。相互作用把 +\(N=2,3\) 的 minima 明显推移。尤其 \(N=3\) gap 很深,但 bath operator 的 +闭系统 brightness 仍为 \(O(1)\),已经预示它可能不是 selection-rule dark +channel。 + +## 4. UniformTEMPO transfer-pole pilot + +只运行 \(N=3\) 的三点: + +\[ +\xi=2.85,\quad3.05,\quad3.20. +\] + +统一数值控制为: + +\[ +M=60,\quad \epsilon=10^{-6},\quad N_\phi=3,\quad +\tau_{\max}=12T,\quad K=8. +\] + +共同浴参数为 + +\[ +\alpha=0.05,\qquad\omega_c=2.5,\qquad T_B=0. +\] + +UniformTEMPO 固定在 revision +`b76a018c32e5415989761d902b1b0e95f1a337da`,生产代码 snapshot 为 +`b42725363a01e2ca88b951c3d28026df8104aa40`。 + +| \(\xi\) | \(\int d\omega\,|\bar j|\) | visible residue | dominant residue | pole fit residual | connected tail | +|---:|---:|---:|---:|---:|---:| +| 2.85 | 0.0098063 | 0.45465 | 0.24109 | 0.04171 | 0.12827 | +| 3.05 | 0.0089765 | 0.98121 | 0.53697 | 0.04452 | 0.19073 | +| 3.20 | 0.0087470 | 1.52779 | 0.82761 | 0.06341 | 0.20993 | + +三点 bond dimension 均为 13。trace error 约 \(8\times10^{-5}\), +Hermiticity error 约 \(10^{-10}\),fixed-point residual 约 +\(1.2\times10^{-4}\)。最大 eigenpair residual 为 +\(7.04\times10^{-11}\),最大 pole modulus 为 0.99087;因此 Krylov +eigenpairs 和单位圆检查通过。 + +三点的 connected tail 均未降到 0.05 以下,右侧点的 pole reconstruction +也略高于 5%。所以这些数据被诚实标为 pilot / unconverged,而不是最终 +精确热谱。 + +## 5. 为什么当前证据足以停止扩展 + +长时间窗可能改变有限窗口 Fourier 积分的精确数值,因此本项目不把三点热谱 +称为完全收敛的负结果。但停止九点扩展不依赖这种细节: + +1. \(\xi=3.05\) 相对右侧的 heat ratio 为 1.026,不存在 minimum; +2. \(\xi=3.05\) 相对较弱 residue flank 的 ratio 为 2.158,方向与 + suppression 相反; +3. dominant residue 也单调增大,不是多个小 residues 求和造成的假象; +4. 前两个点的 pole reconstruction 已低于 5%,但 residue 增强已经超过 + 两倍。 + +要从这些数据得到十倍 residue suppression,需要的不是数值微调,而是改变 +物理机制。因此继续延长三点的 correlation window,或把相同假设扩到 +\(N=1,2\) 的六个昂贵点,不能通过 go/no-go 的资源合理性门槛。 + +## 6. 物理解读 + +高频纵向驱动给出近似 + +\[ +\Omega_{\rm eff}\simeq \Omega J_0(2A/\omega_d). +\] + +在铁磁 cluster 中,cat tunneling gap 可以比单自旋 gap 更快塌缩。但共同浴 +同样通过 \(M_z\) 耦合;在 cat basis 中,\(M_z\) 并不会因 tunneling gap +变小而自动失去矩阵元。这个 pilot 直接展示了两件事必须分开: + +\[ +\text{quasienergy collapse} +\quad\text{与}\quad +\text{bath-dark observable residue}. +\] + +本例中前者极强,后者反而增强。这是比单纯画一条低热流曲线更有价值的 +诊断:它排除了“看到小 gap 就宣称 many-body dark channel”的常见误判。 + +## 7. 当前最合理的后续方向 + +不建议为同一 heat-valve 假设提交集群。若继续 Issue #123,优先级应改为: + +1. 把 transfer-pole/residue decomposition 用于已经收敛的 \(N=3\) + even/odd 热谱,建立 peak–pole 对应; +2. 搜索真正满足 + \(S_{\alpha\beta}^{(m)}=0\) 的 symmetry-protected operator/drive + 组合,而不是只搜索 quasienergy crossing; +3. 只有新的闭系统预扫描同时显示 bright matrix element suppression,才运行 + 新的 UniformTEMPO pilot; +4. 不扩展 \(N=4\),也不做 thermodynamic 或 critical claim。 + +## 8. 可复现命令 + +```bash +.venv/bin/python -m floquet_if_manybody.cli heat-valve \ + --pilot \ + --output results/heat-valve \ + --cache results/cache/uniform_tempo \ + --figures figures/heat-valve + +.venv/bin/python -m floquet_if_manybody.cli \ + heat-valve-audit results/heat-valve +``` + +第二条命令预期返回非零,因为 scientific claim gates 被正确拒绝。完整输入、 +三点结果、manifest、PNG/PDF 和审计失败原因均已保留。 diff --git a/tracks/mps/solutions/Ranger-123/docs/methods.md b/tracks/mps/solutions/Ranger-123/docs/methods.md new file mode 100644 index 000000000..eb2df3a0a --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/docs/methods.md @@ -0,0 +1,203 @@ +# Numerical methods and evidence labels + +## Exact finite-system layer + +`exact_diagonalization` constructs the operators in the full computational +basis and projects them with orthonormal isometries obtained from the exact +swap/reflection operators. Tests require sector leakage below \(10^{-13}\). +The resulting physical dimensions are \(d=3\) for the \(N=2\) triplet, +\(d=6\) for the \(N=3\) reflection-even sector, and \(d=2\) for the +reflection-odd sector. + +`closed_unitary` uses midpoint time ordering, + +\[ +U(T)\approx\prod_n\exp[-iH(t_{n+1/2})\delta t], +\] + +and supplies Floquet modes and Fourier matrix elements. Tests cover unitarity, +the static limit, quasienergy-zone placement, and second-order timestep +convergence. + +## Uniform influence-functional backend + +The production method label is `uniform_tempo_floquet_multitime`. +[UniformTEMPO.jl](https://github.com/uniformTEMPO/UniformTEMPO.jl) is pinned in +`julia/Project.toml` pins the package source to revision +`b76a018c32e5415989761d902b1b0e95f1a337da`. + +For the zero-temperature Ohmic bath, + +\[ +J_B(\omega)=\alpha\omega e^{-\omega/\omega_c},\qquad +C_B(t)=\frac{\alpha\omega_c^2}{(1+i\omega_ct)^2}. +\] + +The Julia runner builds or reloads a serialized uniform process tensor, forms +the periodic influence-functional transfer sequence, solves its extended +Floquet fixed point, and evaluates two-time correlations by inserting +left-acting system operators into the extended state. It does not apply the +quantum regression theorem to a reduced density matrix. + +The period average uses phase samples across one drive cycle. The correlation +is decomposed as + +\[ +\bar C(\tau)=C_{\rm dec}(\tau)+C_{\rm coh}(\tau). +\] + +Only \(C_{\rm dec}\) is numerically Fourier integrated. Fourier coefficients +of \(\langle S(t)\rangle\) are stored separately as analytic coherent delta +weights, preventing finite-window broadening. + +## Nested convergence controller + +The publication schedule is: + +| control | ladder | +|---|---| +| steps per period | \(60,90,120\) | +| uniform compression tolerance | \(3\times10^{-7},10^{-7},3\times10^{-8}\) | +| phase samples | \(3,15\) | + +The inexpensive \(N=2\) error grid may continue the tolerance ladder through +\(10^{-8}\) and \(3\times10^{-9}\). + +At each timestep grid the controller first establishes compression +convergence. A timestep comparison is accepted only if compression passed on +both participating grids. Phase refinement is performed only after a +timestep comparison passes. The residual limits are + +\[ +r_\rho\le0.05,\qquad r_C\le0.08,\qquad r_j\le0.08. +\] + +Final physical gates require: + +- fixed-point residual \(\le10^{-3}\); +- trace and Hermiticity errors \(\le5\times10^{-3}\); +- connected-correlation tail \(\le0.05\); +- minimum density eigenvalue \(\ge-5\times10^{-3}\). + +Every comparison records the coarse/refined fingerprints, steps, tolerance, +phase count, bond dimensions, three residuals, and pass/fail status. The cache +uses atomic replacement so interrupted calculations can be resumed safely. + +## Production outcomes + +### \(N=3\) sector grid + +All six points pass the nested controller and physical gates: + +| sector | \(J/\Omega\) | final steps | bond | timestep \(r_j\) | tail | +|---|---:|---:|---:|---:|---:| +| even | 0.25 | 90 | 40 | 0.00844 | 0.02792 | +| even | 0.50 | 90 | 43 | 0.02799 | 0.00562 | +| even | 1.00 | 120 | 46 | 0.04890 | 0.01077 | +| odd | 0.25, 0.50, 1.00 | 90 | 16 | 0.00151 | 0.03986 | + +The projected odd-sector Hamiltonian and coupling are exactly \(J\) +independent; the three production spectra have relative maximum difference +zero. + +### \(N=2\) exact-vs-Markov grid + +The calibration grid contains all nine combinations of +\(\alpha=0.025,0.05,0.1\) and +\(\omega_d/\Delta_g=0.75,1,1.25\). All nine exact points pass the production +gates. The weak-coupling delay window grows as +\(\max(4,\lceil0.3/\alpha\rceil)\) periods. + +The three reported errors are: + +\[ +D_\rho=\tfrac12\|\rho_{\rm IF}-\rho_{\rm ME}\|_1, +\] + +\[ +\epsilon_C=\frac{\int d\tau\,|C_{\rm IF}-C_{\rm ME}|} +{\int d\tau\,|C_{\rm IF}|},\qquad +\epsilon_j=\frac{\int d\omega\,|\bar j_{\rm IF}-\bar j_{\rm ME}|} +{\int d\omega\,|\bar j_{\rm IF}|}. +\] + +Across the grid, \(D_\rho=0.9968\)–\(0.9997\), +\(\epsilon_C=1.10\)–\(1.86\), and +\(\epsilon_j=5.03\)–\(68.84\). These large values are a result, not a +convergence failure: the uniform-TEMPO reference points themselves are +converged. + +### \(N=3\) same-model exact-vs-Markov grid + +The six converged \(N=3\) sector points are also compared to +Floquet-Markov/QRT without changing the Hamiltonian, drive, bath, sector, or +frequency and delay grids. In the even sector, the heat-spectrum error is +9.02–11.72 and the trace distance is 0.992–1.000. In the exactly +\(J\)-independent odd sector, all three rows reproduce the same errors: +\(D_\rho=0.4753\), \(\epsilon_C=0.5666\), and \(\epsilon_j=0.3920\). +This closes the same-model comparison required by Tier 3 rather than using the +\(N=2\) calibration as a proxy. + +### Model-definition variants + +At \(N=3,J/\Omega=0.5,\alpha=0.1\), the bounded variants +\(S=M_z/3\), with and without \(+\alpha\omega_cS^2\), pass compression, +timestep, phase, and physical gates (final bond dimension 45). + +The Kac variants \(S=M_z/\sqrt3\) pass the compression comparison: + +| variant | refined bond | compression \(r_j\) | +|---|---:|---:| +| Kac, no counterterm | 82 | 0.02373 | +| Kac, counterterm | 81 | 0.00502 | + +Their next timestep and phase layers are intentionally not run by the local +default because the larger coupling raises the process-tensor cost sharply. +They are labeled `local_resource_ceiling`, not converged. `--full-kac` +continues the same auditable ladder on a cluster. + +## Independent validation + +`scripts/run_fig3_validation.py` downloads the immutable author archive from +Zenodo, verifies MD5 `0f3f9d9d8538aa96aee089973df7d9c2`, and independently +recomputes all three transversal-drive curves in Fig. 3 (bottom) at +\(\omega_d/\Omega=1,1.5,2\). All three points pass the same density, fixed-point, +Hermiticity, trace, and correlation-tail gates. The normalized shape +\(L^1\) discrepancies are 0.0562, 0.2736, and 0.3718 respectively; these are +reported as quantitative structural reproduction, not bitwise identity. + +`scripts/run_uniform_validation.py` performs: + +- a single-spin UniformTEMPO smoke calculation that passes its declared + physical gates (bond 19, fixed-point residual \(1.40\times10^{-4}\)); +- a coarse reflection-odd UniformTEMPO–OQuPy comparison. + +The latter has heat \(L^1\) difference 0.517 and is retained as an independent +implementation diagnostic, not as a convergence claim. It also verifies the +projected odd-sector \(J\)-invariance exactly. + +## Benchmark and legacy backends + +`floquet_markov` / `floquet_markov_qr` implement a Born-Markov, fully secular +periodic Lindblad benchmark with QRT correlations. They are never labeled +non-Markovian. + +The in-repository finite-memory QUAPI code exposes the exponential +\(O[(d^2)^{K+1}]\) wall and is used for regression tests. OQuPy 0.5.0 remains +available as an independent PT-TEMPO validation backend. Neither supplies the +production values in `results/paper`. + +## N=4 convergence-gated extension + +The generic reflection projector produces \(10\oplus6\) blocks for \(N=4\). +At \(J/\Omega=0.25\), the odd-sector point passes nested compression, +timestep, phase, and physical gates at 60 steps per period, tolerance +\(3\times10^{-6}\), and 15 phases (bond 8, connected tail 0.0368). Its same-model +Floquet-Markov/QRT heat-spectrum error is 3.405. The exact continuous spectrum +has its three largest peaks at 0.4725, 0.9600, and 1.4400 \(\Omega\), whereas +the selected drive frequency is 0.9256 \(\Omega\). + +The even-sector three-period endpoint missed only the connected-tail gate +(0.0552 against 0.05). Without weakening the gate, the production point was +rerun with six delay periods. It then passed every layer at bond 13 with tail +0.0213; its same-model heat-spectrum error is 5.494. diff --git a/tracks/mps/solutions/Ranger-123/docs/report-zh.md b/tracks/mps/solutions/Ranger-123/docs/report-zh.md new file mode 100644 index 000000000..898fb2775 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/docs/report-zh.md @@ -0,0 +1,295 @@ +# Issue #123:\(N=2,3\) 完整实施与数值结果 + +## 1. 最终结论 + +本项目已经把 Issue #123 的 \(N=2,3\) 部分做成可运行、可恢复、可审计的 +研究实现。生产计算使用公开的 +[UniformTEMPO.jl](https://github.com/uniformTEMPO/UniformTEMPO.jl),固定在 +revision `b76a018c32e5415989761d902b1b0e95f1a337da`;OQuPy 只作为独立粗粒度 +交叉检查。 + +完成状态如下: + +1. \(N=2,3\) 的解析对称性、暗 sector、能隙和谱权重已由精确对角化与单元 + 测试验证。 +2. \(N=3\) 的 reflection-even/odd 六个热谱点全部通过 compression、 + timestep、15 相位以及 density-matrix 物理性门槛。 +3. \(N=2\) 的 \(3\times3\) exact-vs-Floquet-Markov 网格九个点全部收敛, + 同时给出稳态、两时间关联和热谱三种误差。 +4. bounded normalization 的有/无 counterterm 两个模型全部收敛。 +5. Kac normalization 的两个模型已通过压缩收敛,但完整 timestep/phase + 认证需要集群;结果明确标成 `local_resource_ceiling`。 +6. 论文 Fig. 3 底图的三个横向驱动频率全部由独立 UniformTEMPO 计算通过 + 物理门槛,并与作者公开数据做了定量谱形和面积比较。 +7. \(N=4\) reflection-odd 点通过完整收敛阶梯,并完成同模型 + Floquet-Markov/QRT 对照;heat error 为 3.405。 +8. \(N=4\) reflection-even 点在不放宽尾门槛的情况下将关联窗从三周期 + 扩展到六周期并收敛;heat error 为 5.494。 + +所以,对“需要提交集群吗”的答案是: + +- 核心 \(N=2,N=3\) 结论不需要,已经在本地完成; +- 只有可选的 Kac 全收敛扩展需要集群; +- \(N=4\) odd-sector 的有限尺寸试算已经完成;热力学连续谱和临界幂律 + 不属于这次完成范围,也没有被暗示为已解决。 + +## 2. 模型与数值方法 + +系统 Hamiltonian 为 + +\[ +H_0=-J\sum_{i=1}^{N-1}Z_iZ_{i+1} + +\frac{\Omega}{2}\sum_iX_i, +\] + +驱动和共同浴通过 + +\[ +S_N=\eta_N\sum_i Z_i +\] + +耦合。基准采用 \(\Omega=1\)、零温 Ohmic bath + +\[ +J_B(\omega)=\alpha\omega e^{-\omega/\omega_c},\qquad \omega_c=2.5. +\] + +生产 backend 构造 uniform influence tensor,并在扩展的 +system-Liouville × environment-memory 空间中求 Floquet fixed point。两时间 +关联通过在扩展态中插入左作用算符得到,不使用 reduced-state quantum +regression theorem。 + +周期平均关联拆成 + +\[ +\bar C(\tau)=C_{\rm dec}(\tau)+C_{\rm coh}(\tau). +\] + +\(C_{\rm dec}\) 数值 Fourier 积分得到连续热谱;\(\langle S(t)\rangle\) 的 +Fourier 系数单独转成解析 delta 权重,避免有限时间窗制造假展宽。 + +### 收敛规则 + +uniform 计算采用嵌套阶梯: + +\[ +M=60,90,120,\qquad +\epsilon=3\times10^{-7},10^{-7},3\times10^{-8},\qquad +N_\phi=3,15. +\] + +每个 timestep 网格必须先通过 compression 收敛,之后才能参与 timestep +比较;最后再做相位细化。门槛为 + +\[ +r_\rho\le0.05,\qquad r_C\le0.08,\qquad r_j\le0.08. +\] + +最终还要求 fixed-point residual \(\le10^{-3}\)、迹与厄米误差 +\(\le5\times10^{-3}\)、关联尾 \(\le0.05\)、最小 density eigenvalue +\(\ge-5\times10^{-3}\)。 + +## 3. \(N=2\):解析结构 + +交换对称性将四维 Hilbert space 分成三维 triplet 与一维 singlet。 + +\[ +S_2|s\rangle=0 +\] + +且 Hamiltonian 不泄漏 singlet,因此 singlet 是 collective drive 和 common +bath 下的严格暗态。生产热谱直接投影到 triplet,避免完整空间中 steady +state 非唯一。 + +triplet 的两条 collective gaps 为 + +\[ +\Delta_{\rm low/high}=\sqrt{J^2+\Omega^2}\mp J, +\] + +权重为 + +\[ +W_{\rm low/high}=2\eta_2^2 +\left(1\pm\frac{J}{\sqrt{J^2+\Omega^2}}\right). +\] + +数值与解析式的最大偏差为 \(1.55\times10^{-15}\)。强铁磁区出现强的低频 +collective-cat transition 和逐渐变暗的高频支路。 + +![N=2 exact spectrum](../figures/n2_exact.png) + +## 4. \(N=3\):最小 many-body onset + +open chain 的 edge reflection 给出 + +\[ +\mathcal H=\mathcal H_{R=+}^{(6)}\oplus\mathcal H_{R=-}^{(2)}. +\] + +odd sector 是 edge singlet 与中央自旋组成的嵌入单自旋模型,投影后的 +Hamiltonian 和耦合算符严格独立于 \(J\)。even sector 则等价于一个 +spin-1 edge 与中央 spin-\(\tfrac12\) 的六维模型。 + +在强铁磁区,even sector 的主 gap 满足 + +\[ +\Delta_{\rm cat}^{(3)}\simeq\frac{\Omega^3}{4J^2}. +\] + +在 \(J=16\Omega\) 时, + +\[ +\frac{4J^2\Delta_g}{\Omega^3}=0.999023, +\] + +bright weight 为 0.999267。 + +![N=3 exact collective gap](../figures/n3_exact.png) + +### 六点热谱 + +\(J/\Omega=0.25,0.5,1\) 的 even/odd 六点全部收敛: + +| sector | \(J/\Omega\) | 最终 \(M\) | bond | timestep \(r_j\) | 关联尾 | +|---|---:|---:|---:|---:|---:| +| even | 0.25 | 90 | 40 | 0.00844 | 0.02792 | +| even | 0.50 | 90 | 43 | 0.02799 | 0.00562 | +| even | 1.00 | 120 | 46 | 0.04890 | 0.01077 | +| odd | 0.25, 0.50, 1.00 | 90 | 16 | 0.00151 | 0.03986 | + +even 主峰随 \(J\) 增大向低频移动并出现更多结构;odd 三条曲线完全重合, +相对最大差为 0。这同时验证了 sector 推导和数值实现。 + +![N=3 converged sector heat](../figures/paper/n3_sector_heat.png) + +## 5. Floquet-Markov 在哪里失败 + +\(N=2\) interacting triplet 的校准网格为 + +\[ +\alpha=0.025,0.05,0.1,\qquad +\omega_d/\Delta_g=0.75,1,1.25. +\] + +弱耦合时相关时间窗按 + +\[ +N_{\rm delay}=\max(4,\lceil0.3/\alpha\rceil) +\] + +增长;必要时 compression tolerance 深化到 \(10^{-8}\) 或 +\(3\times10^{-9}\)。九个 UniformTEMPO 点全部通过最终审计。 + +三种误差范围为: + +\[ +D_\rho=0.9968\text{--}0.9997, +\] + +\[ +\epsilon_C=1.10\text{--}1.86, +\qquad +\epsilon_j=5.03\text{--}68.84. +\] + +这些值说明在所选参数层中 Floquet-Markov/QRT 与非马尔可夫参考严重不符。 +特别是热谱误差远大于 reduced-state 指标能够直观表达的差异,因此仅比较 +单时 observables 不能验证 calorimetry。 + +![Converged exact-vs-Markov error map](../figures/paper/error_maps.png) + +为消除“Tier 2 是 \(N=3\),但误差图只在 \(N=2\)”的尺寸错位,现进一步 +对六个已经收敛的 \(N=3\) sector 点逐一运行完全相同模型和参数的 +Floquet-Markov/QRT。六点全部完成:even sector 的 +\(D_\rho=0.9921\text{--}0.9999\)、\(\epsilon_C=1.024\text{--}1.153\)、 +\(\epsilon_j=9.02\text{--}11.72\);odd sector 因投影模型严格 +\(J\)-不变,三点均给出 \(D_\rho=0.4753\)、\(\epsilon_C=0.5666\)、 +\(\epsilon_j=0.3920\)。这构成 Tier 3 所要求的 same-model 定量闭环。 + +![N=3 same-model exact-vs-Markov map](../figures/paper/n3_error_maps.png) + +## 6. 暗通道与模型定义 + +Floquet matrix elements 计算到 \(|m|\le40\),Parseval 残差保持在机器精度。 +结合 \(\bar j(\omega)\) 与 \(\overline{\mathrm{Var}(S)}\) 可以区分小矩阵元 +候选、collective fluctuation 与真正的 heat suppression;不能简单以“纠缠 +较强”等价于“暗”。 + +![Floquet diagnostics](../figures/paper/dark_diagnostics.png) + +对 \(N=3,J/\Omega=0.5,\alpha=0.1\),比较 + +\[ +S=M_z/3,\qquad S=M_z/\sqrt3 +\] + +以及有/无 + +\[ +H_{\rm ct}=+\alpha\omega_c S^2. +\] + +bounded 两点完整收敛,最终 bond 均为 45。Kac 两点的压缩比较通过: + +- no counterterm:bond 63→82,热残差 0.02373; +- counterterm:bond 63→81,热残差 0.00502。 + +Kac 曲线仍以虚线显示,表示 timestep/phase 未完成,而不是表示压缩失败。 +这已经证明 normalization 和 counterterm 会改变数值难度与谱形,不能只在 +画图阶段重标度。 + +![Model variants](../figures/paper/model_variants.png) + +## 7. 独立验证 + +对于原论文 Fig. 3 底图,脚本先校验 Zenodo 归档 MD5,再在 +\(\omega_d/\Omega=1,1.5,2\) 上独立运行。三个点的归一化谱形 \(L^1\) 误差 +分别为 0.0562、0.2736、0.3718,积分强度比分别为 1.0268、1.1622、 +1.3119;全部通过 fixed-point、迹、厄米性、正定性和关联尾门槛。这是 +定量结构复现,而不是逐点完全相同的声明。 + +![Fig. 3 bottom independent reproduction](../figures/validation/fig3_transversal_summary.png) + +单自旋 UniformTEMPO smoke test 的 bond 为 19,fixed-point residual 为 +\(1.40\times10^{-4}\),关联尾为 0.00555,全部通过物理门槛。 + +粗粒度 UniformTEMPO–OQuPy 对照得到 phase-state Frobenius difference +0.374、correlation \(L^1\) difference 0.282、heat \(L^1\) difference +0.517。由于两边离散化和压缩设置都很粗,该结果只作为独立实现诊断,不作为 +两种 backend 已收敛一致的声明。投影后的 odd-sector \(J\) 不变性在该检查中 +仍精确成立。 + +## 8. \(N=4\) 收敛门控结果 + +通用 reflection projector 将 \(N=4\) 分成 even 10 维和 odd 6 维子空间。 +在 \(J/\Omega=0.25\) 上,odd sector 通过 compression、30→60 步时间网格、 +3→15 相位和全部物理门槛;最终 bond 为 8,关联尾为 0.0368。同模型 +Floquet-Markov/QRT 的 \(\epsilon_j=3.405\)。连续谱三个最强峰位于 +0.4725、0.9600、1.4400 \(\Omega\),相对于 0.9256 \(\Omega\) 驱动呈现 +多峰 collective structure。 + +even sector 的三周期计算仅关联尾 0.0552 未过 0.05 门槛。保持原门槛并将 +关联窗扩展到六周期后,最终 bond 为 13、尾幅为 0.0213,全部自适应证据 +通过;同模型 \(\epsilon_j=5.494\)。 + +![N=4 odd same-model comparison](../figures/paper/n4_odd_j0p25_comparison.png) + +![N=4 even same-model comparison](../figures/paper/n4_even_j0p25_comparison.png) + +## 9. 如何彻底续跑 + +本地默认命令已经完成论文主网格。若有集群,只需继续 Kac 阶梯: + +```bash +FULL_KAC=1 PYTHON_BIN=.venv/bin/python \ + scripts/run_paper_extension.sh models +``` + +cache key 包含全部物理/数值参数与 solver revision,已有点会被安全复用。 +下一阶段若扩大研究范围,推荐顺序为: + +1. 完成 Kac 的 \(M=90,120\) 与 15 相位认证; +2. 再考虑 \(N=4\) 的 matrix-free / Krylov 实现; +3. 只有具备多个尺寸的 finite-size scaling 后,才讨论 continuum 或临界 + power law。 diff --git a/tracks/mps/solutions/Ranger-123/docs/theory.md b/tracks/mps/solutions/Ranger-123/docs/theory.md new file mode 100644 index 000000000..e63883886 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/docs/theory.md @@ -0,0 +1,115 @@ +# Theory and conventions + +## Model + +All baselines use an open chain and \(\hbar=k_B=1\): + +\[ +H_0=-J\sum_{i=1}^{N-1}Z_iZ_{i+1} + +\frac{\Omega}{2}\sum_{i=1}^N X_i , +\qquad +H(t)=H_0+\epsilon_d\cos(\omega_dt)S_N . +\] + +The bath couples through + +\[ +S_N=\eta_N\sum_iZ_i. +\] + +The default `bounded` normalization is \(\eta_N=1/N\). `kac` means +\(\eta_N=1/\sqrt N\), and `collective` means \(\eta_N=1\). +An optional counterterm is specified by its explicit coefficient +`counterterm_strength`; setting it to \(\Lambda\) adds \(+\Lambda S_N^2\). + +The zero-temperature Ohmic convention is + +\[ +J_B(\omega)=\alpha\omega e^{-\omega/\omega_c},\qquad +C_B(t)=\frac{\alpha\omega_c^2}{(1+i\omega_ct)^2}. +\] + +OQuPy defines its power-law spectral density with a factor of two. The wrapper +therefore passes `alpha/2`; this is tested and recorded in every PT-TEMPO result. + +## N=2 + +Exchange symmetry decomposes the Hilbert space as + +\[ +\mathcal H=\mathcal H_{\rm triplet}\oplus\mathcal H_{\rm singlet}. +\] + +The singlet is dark: + +\[ +S_2|s\rangle=0,\qquad H(t)|s\rangle=J|s\rangle. +\] + +In the triplet, define \(E=\sqrt{J^2+\Omega^2}\). The three energies are +\(-E,-J,+E\), so the two allowed gaps and weights are + +\[ +\Delta_{\rm low/high}=E\mp J, +\] + +\[ +W_{\rm low/high}=2\eta_2^2(1\pm J/E). +\] + +All four formulas are compared with numerical diagonalization for multiple +values of \(J\) at machine precision. + +## N=3 + +Reflection \(1\leftrightarrow3\) gives + +\[ +\mathcal H=\mathcal H_{R=+}\oplus\mathcal H_{R=-},\qquad 8=6+2. +\] + +The odd sector is the edge singlet times the central spin. The Ising term +vanishes in this sector, so its gap is exactly \(\Omega\) for every \(J\). The +even sector contains the collective physics. At strong ferromagnetic coupling, +the lowest bright transition obeys + +\[ +\Delta_{\rm cat}=\frac{\Omega^3}{4J^2}+O(J^{-4}), +\] + +and its normalized bright weight tends to one. + +## Heat-current convention + +For the period-averaged collective correlation + +\[ +\bar C(\tau)=T^{-1}\int_0^Tdt\, +\langle S(t+\tau)S(t)\rangle , +\] + +the continuous heat-current density is + +\[ +\bar j_{\rm con}(\omega)=2J_B(\omega)\omega +\int_0^\infty d\tau\left[ +\cos(\omega\tau)\operatorname{Re}C_{\rm con}(\tau) ++(1+2n_B)\sin(\omega\tau)\operatorname{Im}C_{\rm con}(\tau) +\right]. +\] + +The factorized asymptotic part is never windowed into fake finite-width peaks. +If + +\[ +\langle S(t)\rangle=\sum_nm_ne^{-in\omega_dt}, +\] + +its positive-frequency coherent correlation weight is \(2|m_n|^2\), and the +heat delta weight is + +\[ +\pi J_B(n\omega_d)n\omega_d\,2|m_n|^2. +\] + +The JSON schema stores these peaks separately under `delta_peaks`. diff --git a/tracks/mps/solutions/Ranger-123/figures/backend_comparison.pdf b/tracks/mps/solutions/Ranger-123/figures/backend_comparison.pdf new file mode 100644 index 000000000..11d74060d Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/backend_comparison.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/backend_comparison.png b/tracks/mps/solutions/Ranger-123/figures/backend_comparison.png new file mode 100644 index 000000000..e43e67158 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/backend_comparison.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/heat-valve/heat_valve_hero.pdf b/tracks/mps/solutions/Ranger-123/figures/heat-valve/heat_valve_hero.pdf new file mode 100644 index 000000000..1d9c0dc10 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/heat-valve/heat_valve_hero.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/heat-valve/heat_valve_hero.png b/tracks/mps/solutions/Ranger-123/figures/heat-valve/heat_valve_hero.png new file mode 100644 index 000000000..9eb9c166c Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/heat-valve/heat_valve_hero.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/n2_exact.pdf b/tracks/mps/solutions/Ranger-123/figures/n2_exact.pdf new file mode 100644 index 000000000..f13067195 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/n2_exact.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/n2_exact.png b/tracks/mps/solutions/Ranger-123/figures/n2_exact.png new file mode 100644 index 000000000..e8b208a48 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/n2_exact.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/n2_markov_heat.pdf b/tracks/mps/solutions/Ranger-123/figures/n2_markov_heat.pdf new file mode 100644 index 000000000..6a7525172 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/n2_markov_heat.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/n2_markov_heat.png b/tracks/mps/solutions/Ranger-123/figures/n2_markov_heat.png new file mode 100644 index 000000000..2a68cda89 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/n2_markov_heat.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/n2_pt_tempo_heat.pdf b/tracks/mps/solutions/Ranger-123/figures/n2_pt_tempo_heat.pdf new file mode 100644 index 000000000..c3b3fa058 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/n2_pt_tempo_heat.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/n2_pt_tempo_heat.png b/tracks/mps/solutions/Ranger-123/figures/n2_pt_tempo_heat.png new file mode 100644 index 000000000..b940d3f52 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/n2_pt_tempo_heat.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/n3_exact.pdf b/tracks/mps/solutions/Ranger-123/figures/n3_exact.pdf new file mode 100644 index 000000000..b34439ada Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/n3_exact.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/n3_exact.png b/tracks/mps/solutions/Ranger-123/figures/n3_exact.png new file mode 100644 index 000000000..b34b479b3 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/n3_exact.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/n3_pt_tempo_dynamics.pdf b/tracks/mps/solutions/Ranger-123/figures/n3_pt_tempo_dynamics.pdf new file mode 100644 index 000000000..76eea5e45 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/n3_pt_tempo_dynamics.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/n3_pt_tempo_dynamics.png b/tracks/mps/solutions/Ranger-123/figures/n3_pt_tempo_dynamics.png new file mode 100644 index 000000000..ac0989334 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/n3_pt_tempo_dynamics.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/dark_diagnostics.pdf b/tracks/mps/solutions/Ranger-123/figures/paper/dark_diagnostics.pdf new file mode 100644 index 000000000..11ed00059 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/dark_diagnostics.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/dark_diagnostics.png b/tracks/mps/solutions/Ranger-123/figures/paper/dark_diagnostics.png new file mode 100644 index 000000000..8b9997947 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/dark_diagnostics.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/error_maps.pdf b/tracks/mps/solutions/Ranger-123/figures/paper/error_maps.pdf new file mode 100644 index 000000000..c4288bc9a Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/error_maps.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/error_maps.png b/tracks/mps/solutions/Ranger-123/figures/paper/error_maps.png new file mode 100644 index 000000000..87b75bf6e Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/error_maps.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/model_variants.pdf b/tracks/mps/solutions/Ranger-123/figures/paper/model_variants.pdf new file mode 100644 index 000000000..25c9c7a6a Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/model_variants.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/model_variants.png b/tracks/mps/solutions/Ranger-123/figures/paper/model_variants.png new file mode 100644 index 000000000..91425722b Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/model_variants.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/n3_error_maps.pdf b/tracks/mps/solutions/Ranger-123/figures/paper/n3_error_maps.pdf new file mode 100644 index 000000000..f4e7acb65 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/n3_error_maps.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/n3_error_maps.png b/tracks/mps/solutions/Ranger-123/figures/paper/n3_error_maps.png new file mode 100644 index 000000000..1484af2d4 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/n3_error_maps.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/n3_odd_difference.pdf b/tracks/mps/solutions/Ranger-123/figures/paper/n3_odd_difference.pdf new file mode 100644 index 000000000..484712bc2 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/n3_odd_difference.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/n3_odd_difference.png b/tracks/mps/solutions/Ranger-123/figures/paper/n3_odd_difference.png new file mode 100644 index 000000000..c78891d5f Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/n3_odd_difference.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/n3_sector_heat.pdf b/tracks/mps/solutions/Ranger-123/figures/paper/n3_sector_heat.pdf new file mode 100644 index 000000000..bedb5708b Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/n3_sector_heat.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/n3_sector_heat.png b/tracks/mps/solutions/Ranger-123/figures/paper/n3_sector_heat.png new file mode 100644 index 000000000..0d31de66a Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/n3_sector_heat.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/n4_even_j0p25_comparison.pdf b/tracks/mps/solutions/Ranger-123/figures/paper/n4_even_j0p25_comparison.pdf new file mode 100644 index 000000000..30443f6df Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/n4_even_j0p25_comparison.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/n4_even_j0p25_comparison.png b/tracks/mps/solutions/Ranger-123/figures/paper/n4_even_j0p25_comparison.png new file mode 100644 index 000000000..1c6764ab3 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/n4_even_j0p25_comparison.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/n4_odd_j0p25_comparison.pdf b/tracks/mps/solutions/Ranger-123/figures/paper/n4_odd_j0p25_comparison.pdf new file mode 100644 index 000000000..c9cfc242a Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/n4_odd_j0p25_comparison.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/paper/n4_odd_j0p25_comparison.png b/tracks/mps/solutions/Ranger-123/figures/paper/n4_odd_j0p25_comparison.png new file mode 100644 index 000000000..4a8908cda Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/paper/n4_odd_j0p25_comparison.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_summary.pdf b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_summary.pdf new file mode 100644 index 000000000..be3b857f7 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_summary.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_summary.png b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_summary.png new file mode 100644 index 000000000..bd13c1236 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_summary.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd1.pdf b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd1.pdf new file mode 100644 index 000000000..50188e07d Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd1.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd1.png b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd1.png new file mode 100644 index 000000000..44247f7d6 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd1.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd1p5.pdf b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd1p5.pdf new file mode 100644 index 000000000..93a6d8645 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd1p5.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd1p5.png b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd1p5.png new file mode 100644 index 000000000..5f9e67487 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd1p5.png differ diff --git a/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd2.pdf b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd2.pdf new file mode 100644 index 000000000..d30928045 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd2.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd2.png b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd2.png new file mode 100644 index 000000000..52ac2f4b4 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/figures/validation/fig3_transversal_wd2.png differ diff --git a/tracks/mps/solutions/Ranger-123/julia/Project.toml b/tracks/mps/solutions/Ranger-123/julia/Project.toml new file mode 100644 index 000000000..41450d12f --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/julia/Project.toml @@ -0,0 +1,14 @@ +[deps] +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +KrylovKit = "0b1a1467-8014-51b9-945f-bf0ae24f4b77" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" +UniformTEMPO = "df6cb18e-c722-4276-9e4b-1aef7d428362" + +[sources] +UniformTEMPO = {url = "https://github.com/uniformTEMPO/UniformTEMPO.jl.git", rev = "b76a018c32e5415989761d902b1b0e95f1a337da"} + +[compat] +JSON3 = "1" +KrylovKit = "0.10" +OrdinaryDiffEq = "6" +julia = "1.12" diff --git a/tracks/mps/solutions/Ranger-123/julia/run_uniform_tempo.jl b/tracks/mps/solutions/Ranger-123/julia/run_uniform_tempo.jl new file mode 100644 index 000000000..d8f1163b3 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/julia/run_uniform_tempo.jl @@ -0,0 +1,359 @@ +#!/usr/bin/env julia + +if length(ARGS) != 2 + println(stderr, "UniformTEMPO input error: expected input JSON path and output JSON path") + exit(2) +end +if !isfile(ARGS[1]) + println(stderr, "UniformTEMPO input error: input JSON does not exist: $(ARGS[1])") + exit(2) +end + +using JSON3 +using KrylovKit +using LinearAlgebra +using OrdinaryDiffEq +using SHA +using Serialization +using UniformTEMPO + +const METHOD = "uniform_tempo_floquet_multitime" +const UNIFORM_TEMPO_REVISION = "b76a018c32e5415989761d902b1b0e95f1a337da" + +function fail(message::AbstractString) + println(stderr, "UniformTEMPO input error: " * message) + exit(2) +end + +function required(object, key::Symbol) + haskey(object, key) || fail("missing input field: $(key)") + return object[key] +end + +function decode_real_matrix(raw, label::AbstractString) + rows = [Float64.(collect(row)) for row in raw] + isempty(rows) && fail("$(label) must not be empty") + width = length(first(rows)) + width > 0 || fail("$(label) must not have empty rows") + all(length(row) == width for row in rows) || + fail("$(label) rows have inconsistent lengths") + return reduce(vcat, permutedims.(rows)) +end + +function decode_complex_matrix(raw, label::AbstractString) + real_part = decode_real_matrix(required(raw, :real), "$(label).real") + imag_part = decode_real_matrix(required(raw, :imag), "$(label).imag") + size(real_part) == size(imag_part) || + fail("$(label) real and imaginary shapes differ") + return ComplexF64.(real_part, imag_part) +end + +encode_complex(values) = Dict( + "real" => real.(values), + "imag" => imag.(values), + "shape" => collect(size(values)), +) + +function finite_complex(values) + return all(isfinite, real.(values)) && all(isfinite, imag.(values)) +end + +function validate_hermitian(matrix, label::AbstractString) + size(matrix, 1) == size(matrix, 2) || fail("$(label) must be square") + norm(matrix - matrix') <= 1e-10 || fail("$(label) must be Hermitian") +end + +function atomic_json_write(path::AbstractString, payload) + directory = dirname(path) + mkpath(directory) + temporary = path * ".tmp-" * string(getpid()) + open(temporary, "w") do io + JSON3.pretty(io, payload) + write(io, '\n') + end + mv(temporary, path; force=true) +end + +function load_or_build_process_tensor( + coupling, + dt, + bcf, + tolerance; + auto_nc, + memory_cutoff, + truncation, + cap_rank, + low_rank_svd, + max_rank, + cache_path, + cache_key, +) + if cache_path !== nothing && isfile(cache_path) + record = deserialize(cache_path) + record isa AbstractDict || + fail("process tensor cache record is invalid") + get(record, :key, nothing) == cache_key || + fail("process tensor cache key does not match") + get(record, :uniform_tempo_revision, nothing) == UNIFORM_TEMPO_REVISION || + fail("process tensor cache revision does not match") + get(record, :system_dimension, nothing) == size(coupling, 1) || + fail("process tensor cache dimension does not match") + cached_dt = get(record, :dt, nothing) + cached_dt isa Real && isapprox(cached_dt, dt; rtol=1e-13, atol=1e-15) || + fail("process tensor cache timestep does not match") + haskey(record, :process_tensor) || + fail("process tensor cache has no tensor") + return record[:process_tensor], true + end + + pt = uniTEMPO( + coupling, + dt, + bcf, + tolerance; + auto_nc=auto_nc, + n_c=memory_cutoff, + truncation=truncation, + cap_rank=cap_rank, + low_rank_svd=low_rank_svd, + max_rank=max_rank, + ) + if cache_path !== nothing + mkpath(dirname(cache_path)) + temporary = cache_path * ".tmp-" * string(getpid()) + record = Dict( + :key => cache_key, + :uniform_tempo_revision => UNIFORM_TEMPO_REVISION, + :system_dimension => size(coupling, 1), + :dt => dt, + :process_tensor => pt, + ) + open(temporary, "w") do io + serialize(io, record) + end + mv(temporary, cache_path; force=true) + end + return pt, false +end + +function main() + input_path, output_path = ARGS + + input = JSON3.read(read(input_path, String)) + h0 = decode_complex_matrix(required(input, :h0), "h0") + coupling = decode_complex_matrix(required(input, :coupling), "coupling") + drive = decode_complex_matrix(required(input, :drive), "drive") + validate_hermitian(h0, "h0") + validate_hermitian(coupling, "coupling") + validate_hermitian(drive, "drive") + size(h0) == size(coupling) || + fail("h0 and coupling dimensions differ") + size(h0) == size(drive) || + fail("h0 and drive dimensions differ") + + model = required(input, :model) + drive_amplitude = Float64(required(model, :drive_amplitude)) + drive_frequency = Float64(required(model, :drive_frequency)) + drive_frequency > 0 || fail("drive_frequency must be positive") + + bath = required(input, :bath) + alpha = Float64(required(bath, :alpha)) + cutoff = Float64(required(bath, :cutoff)) + temperature = Float64(required(bath, :temperature)) + alpha >= 0 || fail("alpha must be nonnegative") + cutoff > 0 || fail("cutoff must be positive") + temperature == 0 || + fail("the current analytic bath correlation supports temperature=0 only") + + controls = required(input, :controls) + period_steps = Int(required(controls, :steps_per_period)) + tolerance = Float64(required(controls, :tolerance)) + phase_offsets = Int.(collect(required(controls, :phase_offsets))) + delay_steps = Int(required(controls, :delay_steps)) + auto_nc = Bool(required(controls, :auto_nc)) + memory_cutoff = Int(required(controls, :memory_cutoff)) + low_rank_svd = Bool(required(controls, :low_rank_svd)) + truncation = Symbol(String(required(controls, :truncation))) + cap_rank = Int(required(controls, :cap_rank)) + max_rank = Int(required(controls, :max_rank)) + pole_count = Int(required(controls, :pole_count)) + pole_tolerance = Float64(required(controls, :pole_tolerance)) + pole_maxiter = Int(required(controls, :pole_maxiter)) + process_tensor_cache_path = if haskey(controls, :process_tensor_cache_path) + String(controls[:process_tensor_cache_path]) + else + nothing + end + process_tensor_cache_key = if haskey(controls, :process_tensor_cache_key) + String(controls[:process_tensor_cache_key]) + else + nothing + end + + period_steps >= 2 || fail("steps_per_period must be at least two") + 0 < tolerance < 1 || fail("tolerance must lie between zero and one") + delay_steps >= 1 || fail("delay_steps must be positive") + memory_cutoff >= 1 || fail("memory_cutoff must be positive") + cap_rank >= 1 || fail("cap_rank must be positive") + max_rank >= cap_rank || fail("max_rank must be at least cap_rank") + pole_count >= 0 || fail("pole_count must be nonnegative") + pole_tolerance > 0 || fail("pole_tolerance must be positive") + pole_maxiter >= 1 || fail("pole_maxiter must be positive") + truncation in (:rel, :abs) || fail("truncation must be rel or abs") + length(phase_offsets) >= 2 || + fail("at least two phase offsets are required") + length(unique(phase_offsets)) == length(phase_offsets) || + fail("phase offsets must be unique") + all(0 .<= phase_offsets .< period_steps) || + fail("phase offsets must lie within one period") + (process_tensor_cache_path === nothing) == + (process_tensor_cache_key === nothing) || + fail("process tensor cache path and key must be supplied together") + process_tensor_cache_key === nothing || + occursin(r"^[0-9a-f]{64}$", process_tensor_cache_key) || + fail("process tensor cache key must be a SHA-256 digest") + + period = 2π / drive_frequency + dt = period / period_steps + h_s(time) = h0 + drive_amplitude * cos(drive_frequency * time) * drive + bcf(time) = alpha * (cutoff / (1 + im * cutoff * time))^2 + + pt, process_tensor_cache_hit = load_or_build_process_tensor( + coupling, + dt, + bcf, + tolerance; + auto_nc=auto_nc, + memory_cutoff=memory_cutoff, + truncation=truncation, + cap_rank=cap_rank, + low_rank_svd=low_rank_svd, + max_rank=max_rank, + cache_path=process_tensor_cache_path, + cache_key=process_tensor_cache_key, + ) + ptf = floquet_process_tensor(pt, h_s, period) + extended_floquet_state = steadystate(ptf; return_full=true) + floquet_state = reshape( + ptf.v_l * extended_floquet_state, + size(h0), + ) + floquet_state ./= tr(floquet_state) + floquet_transfer = reshape( + ptf.q, + size(ptf.q, 1) * size(ptf.q, 2), + size(ptf.q, 1) * size(ptf.q, 2), + ) + floquet_transfer_residual = norm( + floquet_transfer * extended_floquet_state[:] - + extended_floquet_state[:] + ) / max(norm(extended_floquet_state), eps()) + transfer_eigenvalues = ComplexF64[] + transfer_eigenpair_residuals = Float64[] + if pole_count > 0 + requested = min(pole_count, size(floquet_transfer, 1) - 1) + values_raw, vectors, _ = eigsolve( + floquet_transfer, + requested, + :LM; + tol=pole_tolerance, + maxiter=pole_maxiter, + ) + order = sortperm(abs.(values_raw); rev=true) + length(order) >= requested || + error("KrylovKit returned fewer transfer poles than requested") + selected = order[1:requested] + transfer_eigenvalues = ComplexF64.(values_raw[selected]) + transfer_eigenpair_residuals = [ + norm( + floquet_transfer * vectors[index] - + values_raw[index] * vectors[index] + ) / max(norm(vectors[index]), eps()) + for index in selected + ] + end + + micromotion = evolve(pt, extended_floquet_state, period_steps; h_s=h_s) + extended_after_period = evolve( + pt, + extended_floquet_state, + period_steps; + h_s=h_s, + return_full=true, + ) + fixed_point_residual = norm( + extended_after_period - extended_floquet_state + ) / max(norm(extended_floquet_state), eps()) + + selected_states = [micromotion[offset + 1] for offset in phase_offsets] + one_point = [real(tr(coupling * state)) for state in selected_states] + correlation_records = Vector{Vector{ComplexF64}}() + for offset in phase_offsets + extended_phase_state = if offset == 0 + extended_floquet_state + else + evolve( + pt, + extended_floquet_state, + offset; + h_s=h_s, + return_full=true, + ) + end + h_shifted(time) = h_s(time + offset * dt) + record = two_point_correlations( + pt, + extended_phase_state, + 0, + delay_steps, + coupling, + coupling; + h_s=h_shifted, + ) + push!(correlation_records, ComplexF64.(record)) + end + total_correlation = reduce(+, correlation_records) / length(correlation_records) + + trace_error = maximum(abs(tr(state) - 1) for state in selected_states) + hermiticity_error = maximum(norm(state - state') for state in selected_states) + minimum_density_eigenvalue = minimum( + minimum(eigvals(Hermitian((state + state') / 2))) + for state in selected_states + ) + finite_complex(floquet_state) || error("non-finite Floquet state") + finite_complex(total_correlation) || error("non-finite correlation") + finite_complex(transfer_eigenvalues) || error("non-finite transfer eigenvalue") + all(isfinite, transfer_eigenpair_residuals) || + error("non-finite transfer eigenpair residual") + + manifest_path = joinpath(dirname(Base.active_project()), "Manifest.toml") + payload = Dict( + "method" => METHOD, + "dt" => dt, + "period_steps" => period_steps, + "bond_dimension" => bond_dim(pt), + "floquet_state" => encode_complex(floquet_state), + "phase_states" => encode_complex(cat(selected_states...; dims=3)), + "one_point" => one_point, + "phase_offsets" => phase_offsets, + "delay" => collect(0:delay_steps) .* dt, + "correlation" => encode_complex(total_correlation), + "diagnostics" => Dict( + "trace_error" => trace_error, + "hermiticity_error" => hermiticity_error, + "minimum_density_eigenvalue" => minimum_density_eigenvalue, + "fixed_point_residual" => fixed_point_residual, + "floquet_transfer_residual" => floquet_transfer_residual, + ), + "julia_version" => string(VERSION), + "uniform_tempo_revision" => UNIFORM_TEMPO_REVISION, + "manifest_sha256" => bytes2hex(sha256(read(manifest_path))), + "process_tensor_cache_hit" => process_tensor_cache_hit, + "transfer_eigenvalues" => encode_complex(transfer_eigenvalues), + "transfer_eigenpair_residuals" => transfer_eigenpair_residuals, + "transfer_dimension" => size(floquet_transfer, 1), + ) + atomic_json_write(output_path, payload) +end + +main() diff --git a/tracks/mps/solutions/Ranger-123/julia/smoke_input.json b/tracks/mps/solutions/Ranger-123/julia/smoke_input.json new file mode 100644 index 000000000..e6984ab15 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/julia/smoke_input.json @@ -0,0 +1,31 @@ +{ + "h0": { + "real": [[0.0, 0.5], [0.5, 0.0]], + "imag": [[0.0, 0.0], [0.0, 0.0]] + }, + "coupling": { + "real": [[1.0, 0.0], [0.0, -1.0]], + "imag": [[0.0, 0.0], [0.0, 0.0]] + }, + "model": { + "drive_amplitude": 0.2, + "drive_frequency": 1.0 + }, + "bath": { + "alpha": 0.01, + "cutoff": 2.5, + "temperature": 0.0 + }, + "controls": { + "steps_per_period": 8, + "tolerance": 0.001, + "phase_offsets": [0, 4], + "delay_steps": 4, + "auto_nc": false, + "memory_cutoff": 6, + "low_rank_svd": false, + "truncation": "rel", + "cap_rank": 100, + "max_rank": 1000 + } +} diff --git a/tracks/mps/solutions/Ranger-123/output/pdf/Ranger-123-technical-report.pdf b/tracks/mps/solutions/Ranger-123/output/pdf/Ranger-123-technical-report.pdf new file mode 100644 index 000000000..4941a2c86 Binary files /dev/null and b/tracks/mps/solutions/Ranger-123/output/pdf/Ranger-123-technical-report.pdf differ diff --git a/tracks/mps/solutions/Ranger-123/pyproject.toml b/tracks/mps/solutions/Ranger-123/pyproject.toml new file mode 100644 index 000000000..b2658d6b5 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/pyproject.toml @@ -0,0 +1,51 @@ +[build-system] +requires = ["hatchling>=1.27"] +build-backend = "hatchling.build" + +[project] +name = "floquet-if-manybody" +version = "0.1.0" +description = "Reproducible N=2,3 study for quantum.harness issue 123" +readme = "README.md" +requires-python = ">=3.11" +license = {text = "AGPL-3.0-or-later"} +dependencies = [ + "numpy>=1.26,<2.0", + "scipy>=1.14", + "matplotlib>=3.9", + "PyYAML>=6.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.3", + "ruff>=0.9", + "mypy>=1.14", +] +nonmarkov = [ + "oqupy==0.5.0", +] + +[project.scripts] +floquet-if = "floquet_if_manybody.cli:main" + +[tool.hatch.build.targets.wheel] +packages = ["src/floquet_if_manybody"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "-ra" + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +select = ["E", "F", "I", "UP", "B"] + +[tool.mypy] +python_version = "3.12" +strict = true +ignore_missing_imports = true +disallow_any_generics = false +packages = ["floquet_if_manybody"] diff --git a/tracks/mps/solutions/Ranger-123/report/data-summary.txt b/tracks/mps/solutions/Ranger-123/report/data-summary.txt new file mode 100644 index 000000000..bc3258ea0 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/report/data-summary.txt @@ -0,0 +1,47 @@ +Quantum Harness Issue #123 - Ranger machine-readable summary companion +Generated: 2026-07-30 Asia/Shanghai +UniformTEMPO revision: b76a018c32e5415989761d902b1b0e95f1a337da + +TIER 1 - PUBLISHED FIG. 3 BOTTOM REPRODUCTION +Reference: https://zenodo.org/records/19593671 +Archive MD5: 0f3f9d9d8538aa96aee089973df7d9c2 +All points use dt = pi/60 and delay_periods = 12. + +wd/Omega converged bond tolerance phases tail_amplitude fixed_point_residual hermiticity_error shape_L1 area_ratio +1.0 true 26 1e-7 15 0.0016272564 0.0000808715 0.0036099263 0.0562435 1.0267712 +1.5 true 14 1e-6 4 0.0023604904 0.0005313577 0.0042726147 0.2735650 1.1622298 +2.0 true 14 1e-6 4 0.0156057456 0.0003084472 0.0032007799 0.3718315 1.3118951 + +TIER 2 - N=3 COLLECTIVE HEAT GRID +Six of six reflection-sector points converged for J/Omega = 0.25, 0.5, 1.0. +The reflection-odd projected model and heat curve are exactly J-independent. + +TIER 3 - N=3 SAME-MODEL UNIFORMTEMPO VS FLOQUET-MARKOV/QRT +sector J/Omega trace_distance correlation_error heat_error +even 0.25 0.9996997 1.1529658 9.0167068 +even 0.50 0.9999011 1.0341251 11.7157802 +even 1.00 0.9920892 1.0243259 9.0772348 +odd 0.25 0.4752510 0.5666192 0.3920114 +odd 0.50 0.4752510 0.5666192 0.3920114 +odd 1.00 0.4752510 0.5666192 0.3920114 + +N=4 CONVERGENCE-GATED EXTENSION AT J/OMEGA=0.25 +sector dimension status bond tail_amplitude markov_heat_error +odd 6 converged 8 0.0367791 3.4047954 +even 10 converged 13 0.0213318 5.4937769 + +N=4 odd largest continuous-spectrum peaks (omega/Omega, jbar): +(0.4725, 0.00176277), (0.9600, 0.01938817), (1.4400, 0.00510966) +Selected drive frequency: 0.9256058858 Omega + +Verification at this snapshot: +pytest: 110 passed +ruff: passed +mypy: passed +root result audit: passed (6 files) +paper audit: passed + +Compact JSON evidence: +validation/fig3_transversal_summary.json +validation/n3_same_model_error_map.json +validation/ARTIFACT_PROVENANCE.json diff --git a/tracks/mps/solutions/Ranger-123/report/generated-results.tex b/tracks/mps/solutions/Ranger-123/report/generated-results.tex new file mode 100644 index 000000000..e7d40f4ce --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/report/generated-results.tex @@ -0,0 +1,12 @@ +\newcommand{\TierOneStatus}{3/3 通过物理门槛} +\newcommand{\TierTwoStatus}{$N=3$ 六点、$N=4$ 两 sector 均收敛} +\newcommand{\FigThreeRows}{% +1.0 & 26 & $1.63\times10^{-3}$ & 0.056 & 1.027 \\ +1.5 & 14 & $2.36\times10^{-3}$ & 0.274 & 1.162 \\ +2.0 & 14 & $1.56\times10^{-2}$ & 0.372 & 1.312 \\ +} +\newcommand{\NFourRows}{% +even & 10 & 收敛 & 13 & 0.0213 & 5.494 \\ +odd & 6 & 收敛 & 8 & 0.0368 & 3.405 \\ +} +\newcommand{\NFourConclusion}{两个 sector 的 compression、时间步、相位和物理性门槛全部通过。odd sector 使用三周期关联窗;even sector 的三周期尾幅为 0.0552,因此按门槛扩展到六周期并降至 0.0213。两点均完成同模型 Markov 对照。} diff --git a/tracks/mps/solutions/Ranger-123/report/technical-report.tex b/tracks/mps/solutions/Ranger-123/report/technical-report.tex new file mode 100644 index 000000000..b0c1eaace --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/report/technical-report.tex @@ -0,0 +1,184 @@ +\documentclass[11pt,a4paper]{ctexart} +\usepackage[margin=2.15cm]{geometry} +\usepackage{amsmath,amssymb,booktabs,graphicx,hyperref,longtable,microtype,xcolor} +\usepackage{fancyhdr} +\hypersetup{colorlinks=true,linkcolor=blue!45!black,urlcolor=blue!55!black,citecolor=blue!45!black} +\graphicspath{{../figures/paper/}{../figures/validation/}{../figures/heat-valve/}} +\setlength{\parindent}{2em} +\setlength{\parskip}{0.25em} +\setlength{\headheight}{14pt} +\pagestyle{fancy} +\fancyhf{} +\lhead{Quantum Harness Issue \#123} +\rhead{Team Ranger} +\cfoot{\thepage} +\newcommand{\code}[1]{\texttt{#1}} +\InputIfFileExists{generated-results.tex}{}{\errmessage{generated-results.tex is required}} + +\title{\textbf{共同非马尔可夫浴中的多体 Floquet 热输运}\\ +\large Quantum Harness Issue \#123 技术报告} +\author{Team Ranger\\Chenxi Wan \quad Yedi Shen \quad Junkai Wang} +\date{2026 年 7 月 30 日} + +\begin{document} +\maketitle + +\begin{abstract} +本项目将单自旋 Floquet influence functional 方法扩展到共同浴耦合的相互作用自旋链,给出可恢复、可审计的 uniform TEMPO 数值流程。我们完成原题 Tier 1 的三频率单自旋 Fig.~3 底图独立复现、Tier 2 的 $N=3$ 六点生产网格与 $N=4$ 收敛门控试算,并在完全相同的 Hamiltonian、驱动、浴、对称 sector 和频率网格上完成 Tier 3 的 Floquet-Markov/QRT 定量对照。核心结果是:$N=3$ even sector 的热谱相对误差达到 $9.02$--$11.72$,而 odd sector 为 $0.3920$;这表明弱耦合、无记忆近似的失效具有显著 sector 依赖性。项目同时记录一个预注册的暗通道负结果,避免把能隙塌缩误判为热抑制。所有声明均绑定到机器可读 JSON、图件哈希和自动化门槛。 +\end{abstract} + +\section{赛题要求与完成边界} +Issue \#123 的三级路线分别要求:(i) 复现 Mickiewicz 等人单自旋横向驱动 Fig.~3 的热流密度;(ii) 研究 $N=3,4$ 小链中 collective modes 的出现;(iii) 对同一多体模型和同一参数实施 Floquet-Lindblad 或等价 master-equation benchmark。表~\ref{tab:tier} 按原题而非提交者自定义口径列出证据。 + +\begin{table}[htbp] +\centering +\caption{原题三级路线与本项目证据。} +\label{tab:tier} +\begin{tabular}{p{0.10\linewidth}p{0.54\linewidth}p{0.25\linewidth}} +\toprule +级别 & 交付证据 & 状态 \\ +\midrule +Tier 1 & 公开 Zenodo 曲线的校验下载;$\omega_d/\Omega=1,1.5,2$ 三个独立 UniformTEMPO 点;连续谱和解析 $\delta$ 峰分离 & \TierOneStatus \\ +Tier 2 & $N=3$ even/odd 六点收敛热谱;$N=4$ reflection 分块、回归测试及收敛门控点 & \TierTwoStatus \\ +Tier 3 & $N=3$ 六点在同模型、同参数下与 Floquet-Markov/QRT 比较 $D_\rho,\epsilon_C,\epsilon_j$ & 6/6 收敛 \\ +\bottomrule +\end{tabular} +\end{table} + +本报告不声称热力学极限、连续相变或临界指数。可选 Kac normalization 的完整时间步和相位细化也不被重新标记为已收敛。 + +\section{模型与算法} +系统 Hamiltonian 为 +\begin{equation} +H_0=-J\sum_{i=1}^{N-1}Z_iZ_{i+1}+\frac{\Omega}{2}\sum_{i=1}^{N}X_i, +\qquad +H_{\mathrm d}(t)=\epsilon_{\mathrm d}\cos(\omega_{\mathrm d}t)S_N, +\end{equation} +共同零温 Ohmic 浴经 $S_N=\eta_N\sum_i Z_i$ 耦合,谱密度为 +\begin{equation} +J_B(\omega)=\alpha\omega\exp(-\omega/\omega_c),\qquad \omega_c=2.5\Omega. +\end{equation} +生产后端固定到 UniformTEMPO.jl revision \code{b76a018c32e5}(完整哈希见机器数据)。Python 层完成对称投影、内容寻址缓存、自适应收敛阶梯、扩展 process-tensor state 中的多时间算符插入,以及连续谱与相干 $\delta$ 峰的分离。 + +本实现包含三项面向本题的算法性改进: +\begin{enumerate} +\item 在 system-Liouville 与环境记忆的扩展态中直接插入算符,避免用 reduced-state QRT 冒充非马尔可夫两时间关联; +\item 对 reflection symmetry 做精确分块,$N=3$ 化为 $6\oplus2$,$N=4$ 化为 $10\oplus6$,并把投影算符哈希写入缓存键; +\item 用 compression、时间步、相位和物理性四层门槛控制计算,只有全部通过的点才进入定量图和误差图。 +\end{enumerate} + +\section{Tier 1:单自旋 Fig.~3 独立复现} +公开作者数据来自 Zenodo record \href{https://zenodo.org/records/19593671}{19593671},下载归档 MD5 为 {\scriptsize\texttt{0f3f9d9d8538aa96aee089973df7d9c2}}。独立计算采用论文参数 $\Omega=\epsilon_d=1$、$\alpha=0.05$、$\omega_c=2.5$、$\Delta t=\pi/60$,并将 connected correlation 的 Fourier 连续部分与驱动谐波的解析 $\delta$ 权重分别处理。 + +\begin{table}[htbp] +\centering +\caption{Fig.~3 底图三频率独立计算。$L^1_{\rm shape}$ 比较归一化谱形,$R_{\rm area}$ 为积分强度比。} +\label{tab:fig3} +\begin{tabular}{ccccc} +\toprule +$\omega_d/\Omega$ & bond & 尾幅 & $L^1_{\rm shape}$ & $R_{\rm area}$ \\ +\midrule +\FigThreeRows +\bottomrule +\end{tabular} +\end{table} + +\begin{figure}[htbp] +\centering +\includegraphics[width=\linewidth]{fig3_transversal_summary.png} +\caption{论文 Fig.~3 底图公开数据与独立 UniformTEMPO 连续谱。竖线标出独立计算得到的相干峰位置;比较不把 $\delta$ 峰伪装成有限宽峰。} +\label{fig:fig3} +\end{figure} + +这些曲线是定量的结构复现而非逐点相同。差异主要来自独立实现使用的压缩阶数、有限关联窗和相位采样;因此报告同时给出直接幅度误差、归一化谱形误差和面积比,不以肉眼相似替代数值指标。 + +\section{Tier 2:\texorpdfstring{$N=3$}{N=3} collective heat spectrum} +$N=3$ reflection-even/odd 两个 sector 在 $J/\Omega=0.25,0.5,1$ 上共六点全部通过收敛门槛。odd sector 的投影 Hamiltonian 与耦合算符对 $J$ 严格不变,因此三条曲线在机器精度内重合;even sector 则显示随 $J$ 移动的 collective features。 + +\begin{figure}[htbp] +\centering +\includegraphics[width=0.88\linewidth]{n3_sector_heat.png} +\caption{$N=3$ reflection sector 分辨的频率热流密度。} +\label{fig:n3heat} +\end{figure} + +\section{Tier 3:同模型非马尔可夫与 Markov 对照} +误差定义为 +\begin{align} +D_\rho&=\frac12\lVert\rho_{\rm IF}-\rho_{\rm ME}\rVert_1,\\ +\epsilon_C&=\frac{\int d\tau\,|C_{\rm IF}(\tau)-C_{\rm ME}(\tau)|}{\int d\tau\,|C_{\rm IF}(\tau)|},\\ +\epsilon_j&=\frac{\int d\omega\,|\bar j_{\rm IF}(\omega)-\bar j_{\rm ME}(\omega)|}{\int d\omega\,|\bar j_{\rm IF}(\omega)|}. +\end{align} +even sector 上 $D_\rho=0.9921$--$0.9999$、$\epsilon_C=1.024$--$1.153$、$\epsilon_j=9.02$--$11.72$;odd sector 三点均为 $D_\rho=0.4753$、$\epsilon_C=0.5666$、$\epsilon_j=0.3920$。大误差不是精确端未收敛,而是同参数 Markov benchmark 的真实偏差。 + +\begin{figure}[htbp] +\centering +\includegraphics[width=0.92\linewidth]{n3_error_maps.png} +\caption{$N=3$ 同模型 UniformTEMPO 与 Floquet-Markov/QRT 的三种误差。} +\label{fig:n3error} +\end{figure} + +\section{\texorpdfstring{$N=4$}{N=4} 收敛门控结果} +$N=4$ 采用 reflection-even $10$ 维和 reflection-odd $6$ 维子空间。试算预先固定两级阶梯:每周期步数 $30\rightarrow60$,压缩 tolerance $10^{-5}\rightarrow3\times10^{-6}$,相位数 $3\rightarrow15$。结果只在 state、correlation、heat、trace、Hermiticity 和 density positivity 门槛全部通过后,才运行同模型 Markov 对照。 + +\begin{table}[htbp] +\centering +\caption{$N=4,J/\Omega=0.25$ 的门控试算。} +\label{tab:n4} +\begin{tabular}{cccccc} +\toprule +sector & dim & 状态 & bond & 尾幅 & $\epsilon_j$ \\ +\midrule +\NFourRows +\bottomrule +\end{tabular} +\end{table} + +\NFourConclusion + +\begin{figure}[htbp] +\centering +\begin{minipage}{0.49\linewidth} +\includegraphics[width=\linewidth]{n4_even_j0p25_comparison.png} +\end{minipage}\hfill +\begin{minipage}{0.49\linewidth} +\includegraphics[width=\linewidth]{n4_odd_j0p25_comparison.png} +\end{minipage} +\caption{$N=4$ even(左)和 odd(右)sector 的收敛 UniformTEMPO 热谱及同模型 Floquet-Markov/QRT 对照。odd 连续谱主要峰位于 $0.4725,0.9600,1.4400\,\Omega$,显示相对于驱动频率 $0.9256\,\Omega$ 的多峰 collective structure。} +\label{fig:n4} +\end{figure} + +\section{预注册负结果与创新点} +我们在计算前定义了暗通道继续条件:中心点热流相对两侧至少降低三倍,且可观测 transfer-pole residue 也至少降低三倍。$N=3$ 三点 pilot 中,准能隙虽塌缩,residue 却上升,热流门槛也未通过,因此九点扩展被按规则停止。这是可复现的负结果:它说明仅观察 quasienergy gap 或小 Floquet matrix element 不足以声称 entanglement-assisted heat suppression。 + +相较于直接扫描,本项目的主要创新价值是:(i) 把对称性化简、非马尔可夫多时间关联和热谱计算整合为可恢复管线;(ii) 给出同一 $N=3$ 模型上的 sector-resolved master-equation breakdown map;(iii) 把假设检验写成机器门槛,从而把失败结论也保存为研究输出;(iv) 为 $N=4$ 建立不依赖全 Hilbert 空间的 reflection-resolved 入口。 + +\section{可重复性与数据交付} +仓库提交源代码、测试、PNG/PDF 图、紧凑 JSON 和本报告源文件。体积较大的 \code{results/} 按上游仓库规则不纳入 Git;\code{validation/ARTIFACT\_PROVENANCE.json} 保存本地审计产物的 SHA-256。推荐验证命令为: +\begin{verbatim} +.venv/bin/python -m pytest -q +.venv/bin/python -m ruff check src tests scripts +.venv/bin/python -m mypy src scripts/run_fig3_validation.py +.venv/bin/python -m floquet_if_manybody.cli audit results +.venv/bin/python -m floquet_if_manybody.cli paper-audit results/paper +\end{verbatim} + +\section{结论} +本项目已经从单自旋代码验证推进到 $N=3$ 多体生产计算,并用同模型误差图直接回答了原题最具方法学价值的问题:Floquet-Markov/QRT 的失效不仅显著,而且依赖多体对称 sector。$N=4$ 结果按预先声明的收敛门槛分类,不以“程序跑完”替代“数值可信”。所有正结果和负结果都可以从固定版本后端、缓存键、JSON 指标与图件哈希重新审计。 + +\begin{thebibliography}{9} +\bibitem{mickiewicz2026} +K. Mickiewicz, V. Link, and W. T. Strunz, +``Exact Floquet Dynamics of Strongly Damped Driven Quantum Systems,'' +\emph{Physical Review Letters} \textbf{136}, 200201 (2026). +\bibitem{link2024} +V. Link, H.-H. Tu, and W. T. Strunz, +``Open Quantum System Dynamics from Infinite Tensor Network Contraction,'' +\emph{Physical Review Letters} \textbf{132}, 200403 (2024). +\bibitem{garbellini2026} +M. Garbellini, K. Mickiewicz, V. Link, A. Eisfeld, and W. T. Strunz, +``Uniform process tensor approach for the calculation of multi-time correlation functions of non-Markovian open systems,'' +\emph{Journal of Chemical Physics} (2026), doi:10.1063/5.0331783. +\end{thebibliography} + +\end{document} diff --git a/tracks/mps/solutions/Ranger-123/scripts/instantiate_julia.sh b/tracks/mps/solutions/Ranger-123/scripts/instantiate_julia.sh new file mode 100755 index 000000000..3aa2e7fd1 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/scripts/instantiate_julia.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +julia --project="${project_root}/julia" -e \ + 'using Pkg; Pkg.resolve(); Pkg.instantiate(); Pkg.precompile()' +julia --project="${project_root}/julia" -e \ + 'using UniformTEMPO, OrdinaryDiffEq, JSON3; println("UniformTEMPO environment ready")' diff --git a/tracks/mps/solutions/Ranger-123/scripts/run_baselines.sh b/tracks/mps/solutions/Ranger-123/scripts/run_baselines.sh new file mode 100755 index 000000000..21ac784ce --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/scripts/run_baselines.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu + +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +cd "$project_dir" + +python_bin="${PYTHON_BIN:-$project_dir/.venv/bin/python}" +"$python_bin" -m floquet_if_manybody.cli baselines --output results --figures figures +"$python_bin" -m floquet_if_manybody.cli audit results diff --git a/tracks/mps/solutions/Ranger-123/scripts/run_fig3_validation.py b/tracks/mps/solutions/Ranger-123/scripts/run_fig3_validation.py new file mode 100644 index 000000000..bbbe5bfdf --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/scripts/run_fig3_validation.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +"""Reproduce and compare the transversal single-spin data from PRL Fig. 3.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from dataclasses import asdict +from io import BytesIO +from pathlib import Path +from typing import Any +from urllib.request import urlopen +from zipfile import ZipFile + +import numpy as np +from matplotlib import pyplot as plt +from scipy.integrate import trapezoid + +from floquet_if_manybody.backends.uniform_tempo import ( + UNIFORM_TEMPO_REVISION, + UniformTempoBackend, + UniformTempoControls, +) +from floquet_if_manybody.config import BathConfig, ModelConfig +from floquet_if_manybody.convergence import atomic_write_result, curve_residual +from floquet_if_manybody.heat_current import heat_current_spectrum +from floquet_if_manybody.operators import pauli + +ZENODO_RECORD = "https://zenodo.org/records/19593671" +ZENODO_ARCHIVE = ( + "https://zenodo.org/api/records/19593671/files/" + "exact_floquet_dynamics_of_strongly_damped_driven_quantum_systems.zip/content" +) +ZENODO_MD5 = "0f3f9d9d8538aa96aee089973df7d9c2" +REFERENCE_FREQUENCY = np.arange(0.005, 15.0001, 0.005) + + +def _reference_name(drive_frequency: float) -> str: + label = f"{drive_frequency:g}" + return ( + "fig_3/heat_current_transversal_Ω_1_ϵ_d_1_" + f"ω_d_{label}_α_0.05_ω_c_2.5_bond_dim_235_dt_0.052.csv" + ) + + +def download_reference() -> dict[float, np.ndarray]: + """Download the immutable author archive and extract the three bottom-panel curves.""" + with urlopen(ZENODO_ARCHIVE, timeout=30) as response: # noqa: S310 + archive = response.read() + digest = hashlib.md5(archive, usedforsecurity=False).hexdigest() + if digest != ZENODO_MD5: + raise ValueError(f"Zenodo archive checksum mismatch: {digest}") + curves: dict[float, np.ndarray] = {} + with ZipFile(BytesIO(archive)) as bundle: + for drive_frequency in (1.0, 1.5, 2.0): + values = np.loadtxt(BytesIO(bundle.read(_reference_name(drive_frequency)))) + if values.shape != REFERENCE_FREQUENCY.shape or not np.all(np.isfinite(values)): + raise ValueError(f"invalid Fig. 3 reference curve for wd={drive_frequency:g}") + curves[drive_frequency] = np.asarray(values, dtype=float) + return curves + + +def comparison_metrics(reference: np.ndarray, candidate: np.ndarray) -> dict[str, float]: + """Return direct-amplitude and normalized-shape discrepancies.""" + direct = curve_residual( + REFERENCE_FREQUENCY, + reference, + REFERENCE_FREQUENCY, + candidate, + ) + reference_area = float(trapezoid(abs(reference), REFERENCE_FREQUENCY)) + candidate_area = float(trapezoid(abs(candidate), REFERENCE_FREQUENCY)) + if reference_area <= 0 or candidate_area <= 0: + raise ValueError("Fig. 3 curves must have positive integrated magnitude") + shape = curve_residual( + REFERENCE_FREQUENCY, + reference / reference_area, + REFERENCE_FREQUENCY, + candidate / candidate_area, + ) + return { + "continuous_relative_l1": direct, + "normalized_shape_relative_l1": shape, + "integrated_magnitude_ratio": candidate_area / reference_area, + } + + +def run_point( + drive_frequency: float, + reference: np.ndarray, + cache_directory: Path, + *, + tolerance: float = 1e-6, + phase_samples: int = 4, +) -> dict[str, Any]: + """Run one independent UniformTEMPO point at the published Fig. 3 controls.""" + steps_per_period = int(round(120 / drive_frequency)) + model = ModelConfig( + n=1, + j=0.0, + omega=1.0, + drive_amplitude=1.0, + drive_frequency=drive_frequency, + ) + bath = BathConfig(alpha=0.05, cutoff=2.5, temperature=0.0) + controls = UniformTempoControls( + steps_per_period=steps_per_period, + tolerance=tolerance, + phase_samples=phase_samples, + delay_periods=12, + low_rank_svd=True, + truncation="abs", + cap_rank=5_000, + max_rank=10_000, + ) + run = UniformTempoBackend( + tensor_cache_directory=cache_directory / "process_tensors" + ).run_periodic( + 0.5 * pauli("x"), + pauli("z"), + model, + bath, + controls, + drive_operator=pauli("z"), + ) + heat = heat_current_spectrum(run.correlation, bath, REFERENCE_FREQUENCY) + diagnostics = { + **run.diagnostics, + **run.metadata, + "connected_tail_amplitude": float(abs(run.correlation.connected[-1])), + } + physical_gates_passed = bool( + float(diagnostics["fixed_point_residual"]) <= 1e-3 + and float(diagnostics["trace_error"]) <= 5e-3 + and float(diagnostics["hermiticity_error"]) <= 5e-3 + and float(diagnostics["minimum_density_eigenvalue"]) >= -5e-3 + and float(diagnostics["connected_tail_amplitude"]) <= 5e-2 + ) + return { + "method": "uniform_tempo_fig3_transversal_reproduction", + "converged": physical_gates_passed, + "reference": { + "record": ZENODO_RECORD, + "archive_md5": ZENODO_MD5, + "panel": "Fig. 3 bottom", + }, + "uniform_tempo_revision": UNIFORM_TEMPO_REVISION, + "model": asdict(model), + "bath": asdict(bath), + "controls": asdict(controls), + "diagnostics": diagnostics, + "metrics": comparison_metrics(reference, heat.continuous), + "frequency": heat.frequencies.tolist(), + "reference_continuous": reference.tolist(), + "continuous": heat.continuous.tolist(), + "delta_peaks": [asdict(item) for item in heat.delta_peaks], + } + + +def plot_result(result: dict[str, Any], stem: Path) -> None: + frequency = np.asarray(result["frequency"], dtype=float) + reference = np.asarray(result["reference_continuous"], dtype=float) + candidate = np.asarray(result["continuous"], dtype=float) + figure, axis = plt.subplots(figsize=(7.2, 4.2)) + axis.plot(frequency, reference, label="Mickiewicz et al. Fig. 3 data", lw=1.5) + axis.plot(frequency, candidate, "--", label="independent UniformTEMPO", lw=1.2) + for peak in result["delta_peaks"]: + axis.axvline(float(peak["frequency"]), color="black", alpha=0.35, lw=0.8) + model = result["model"] + axis.set( + xlim=(0, 4), + xlabel=r"bath frequency $\omega/\Omega$", + ylabel=r"continuous $\bar j(\omega)/\Omega$", + title=rf"Fig. 3 bottom validation, $\omega_d={float(model['drive_frequency']):g}\Omega$", + ) + axis.grid(alpha=0.2) + axis.legend(frameon=False) + figure.tight_layout() + stem.parent.mkdir(parents=True, exist_ok=True) + figure.savefig(stem.with_suffix(".png"), dpi=220) + figure.savefig(stem.with_suffix(".pdf")) + plt.close(figure) + + +def plot_summary(results: list[dict[str, Any]], stem: Path) -> None: + """Plot the complete three-curve bottom-panel validation in one artifact.""" + ordered = sorted(results, key=lambda item: float(item["model"]["drive_frequency"])) + figure, axes = plt.subplots(1, 3, figsize=(12.0, 3.55), sharex=True) + for axis, result in zip(axes, ordered, strict=True): + frequency = np.asarray(result["frequency"], dtype=float) + reference = np.asarray(result["reference_continuous"], dtype=float) + candidate = np.asarray(result["continuous"], dtype=float) + model = result["model"] + metrics = result["metrics"] + axis.plot(frequency, reference, label="published data", lw=1.35) + axis.plot(frequency, candidate, "--", label="independent run", lw=1.15) + for peak in result["delta_peaks"]: + if float(peak["frequency"]) <= 4: + axis.axvline(float(peak["frequency"]), color="black", alpha=0.3, lw=0.7) + axis.set( + xlim=(0, 4), + xlabel=r"$\omega/\Omega$", + title=( + rf"$\omega_d={float(model['drive_frequency']):g}\Omega$" + "\n" + rf"shape $L^1={float(metrics['normalized_shape_relative_l1']):.3f}$" + ), + ) + axis.grid(alpha=0.2) + axes[0].set_ylabel(r"continuous $\bar j(\omega)/\Omega$") + axes[0].legend(frameon=False, fontsize=8) + figure.suptitle("Independent reproduction of Mickiewicz et al., Fig. 3 bottom") + figure.tight_layout() + stem.parent.mkdir(parents=True, exist_ok=True) + figure.savefig(stem.with_suffix(".png"), dpi=220) + figure.savefig(stem.with_suffix(".pdf")) + plt.close(figure) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument( + "--drive-frequency", + type=float, + choices=(1.0, 1.5, 2.0), + default=2.0, + ) + parser.add_argument("--plot-only", type=Path) + parser.add_argument("--plot-summary", action="store_true") + parser.add_argument("--tolerance", type=float, default=1e-6) + parser.add_argument("--phase-samples", type=int, default=4) + parser.add_argument( + "--figures", + type=Path, + default=Path("figures/validation"), + ) + parser.add_argument("--output", type=Path, default=Path("results/validation")) + parser.add_argument( + "--cache", + type=Path, + default=Path("results/cache/fig3_uniform_tempo"), + ) + arguments = parser.parse_args() + if arguments.plot_summary: + result_paths = [ + arguments.output / "fig3_transversal_wd1.json", + arguments.output / "fig3_transversal_wd1p5.json", + arguments.output / "fig3_transversal_wd2.json", + ] + results = [json.loads(path.read_text(encoding="utf-8")) for path in result_paths] + if not all(bool(result["converged"]) for result in results): + raise ValueError("all three Fig. 3 points must pass physical gates") + plot_summary(results, arguments.figures / "fig3_transversal_summary") + return 0 + if arguments.plot_only is not None: + result = json.loads(arguments.plot_only.read_text(encoding="utf-8")) + label = f"{float(result['model']['drive_frequency']):g}".replace(".", "p") + plot_result(result, arguments.figures / f"fig3_transversal_wd{label}") + return 0 + references = download_reference() + result = run_point( + arguments.drive_frequency, + references[arguments.drive_frequency], + arguments.cache, + tolerance=arguments.tolerance, + phase_samples=arguments.phase_samples, + ) + arguments.output.mkdir(parents=True, exist_ok=True) + label = f"{arguments.drive_frequency:g}".replace(".", "p") + atomic_write_result(arguments.output / f"fig3_transversal_wd{label}.json", result) + plot_result(result, arguments.figures / f"fig3_transversal_wd{label}") + return 0 if result["converged"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tracks/mps/solutions/Ranger-123/scripts/run_paper_extension.sh b/tracks/mps/solutions/Ranger-123/scripts/run_paper_extension.sh new file mode 100755 index 000000000..1d61c51ac --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/scripts/run_paper_extension.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +PYTHON_BIN="${PYTHON_BIN:-.venv-py312/bin/python}" +TARGET="${1:-all}" +OUTPUT_DIR="${OUTPUT_DIR:-results/paper}" +CACHE_DIR="${CACHE_DIR:-results/cache/uniform_tempo}" +FIGURE_DIR="${FIGURE_DIR:-figures/paper}" +FULL_KAC="${FULL_KAC:-0}" + +run_n3() { + "$PYTHON_BIN" -m floquet_if_manybody.cli n3-heat-grid \ + --output "$OUTPUT_DIR" --cache "$CACHE_DIR" --figures "$FIGURE_DIR" +} + +run_errors() { + "$PYTHON_BIN" -m floquet_if_manybody.cli error-map \ + --output "$OUTPUT_DIR" --cache "$CACHE_DIR" --figures "$FIGURE_DIR" +} + +run_models() { + local extra=() + if [[ "$FULL_KAC" == "1" ]]; then + extra+=(--full-kac) + fi + "$PYTHON_BIN" -m floquet_if_manybody.cli model-comparison \ + --output "$OUTPUT_DIR" --cache "$CACHE_DIR" --figures "$FIGURE_DIR" \ + "${extra[@]}" +} + +case "$TARGET" in + n3) run_n3 ;; + errors) run_errors ;; + models) run_models ;; + all) + run_n3 + run_errors + run_models + "$PYTHON_BIN" -m floquet_if_manybody.cli paper-audit "$OUTPUT_DIR" + ;; + *) + echo "usage: $0 {n3|errors|models|all}" >&2 + exit 2 + ;; +esac diff --git a/tracks/mps/solutions/Ranger-123/scripts/run_pt_baselines.sh b/tracks/mps/solutions/Ranger-123/scripts/run_pt_baselines.sh new file mode 100755 index 000000000..7a3511864 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/scripts/run_pt_baselines.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu + +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +cd "$project_dir" + +python_bin="${PYTHON_BIN:-$project_dir/.venv-py312/bin/python}" +"$python_bin" -m floquet_if_manybody.cli pt-baselines --output results --figures figures +"$python_bin" -m floquet_if_manybody.cli audit results diff --git a/tracks/mps/solutions/Ranger-123/scripts/run_uniform_validation.py b/tracks/mps/solutions/Ranger-123/scripts/run_uniform_validation.py new file mode 100644 index 000000000..127e1727c --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/scripts/run_uniform_validation.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +"""Generate runtime validation records for the pinned UniformTEMPO backend.""" + +from __future__ import annotations + +import argparse +from dataclasses import asdict +from pathlib import Path +from typing import Any, cast + +import numpy as np +from numpy.typing import NDArray + +from floquet_if_manybody.backends.uniform_tempo import ( + UNIFORM_TEMPO_REVISION, + UniformTempoBackend, + UniformTempoControls, +) +from floquet_if_manybody.config import BathConfig, ModelConfig +from floquet_if_manybody.convergence import ( + ConvergenceCache, + atomic_write_result, + curve_residual, + state_residual, +) +from floquet_if_manybody.heat_current import heat_current_spectrum +from floquet_if_manybody.n3_heat import N3HeatPoint, prepare_n3_sector, run_n3_heat_point +from floquet_if_manybody.operators import pauli + + +def _complex_values(values: NDArray[np.complex128]) -> dict[str, Any]: + return { + "real": np.real(values).astype(float).tolist(), + "imag": np.imag(values).astype(float).tolist(), + } + + +def _complex_array(value: dict[str, Any]) -> NDArray[np.complex128]: + return cast( + NDArray[np.complex128], + np.asarray(value["real"], dtype=float) + 1j * np.asarray(value["imag"], dtype=float), + ) + + +def single_spin_record(cache_directory: Path) -> dict[str, Any]: + model = ModelConfig( + n=1, + j=0.0, + omega=1.0, + drive_amplitude=0.2, + drive_frequency=1.0, + ) + bath = BathConfig(alpha=0.05, cutoff=2.5, temperature=0.0) + controls = UniformTempoControls( + steps_per_period=60, + tolerance=1e-6, + phase_samples=3, + delay_periods=6, + low_rank_svd=True, + truncation="abs", + cap_rank=5_000, + max_rank=10_000, + ) + run = UniformTempoBackend( + tensor_cache_directory=cache_directory / "process_tensors" + ).run_periodic(0.5 * pauli("x"), pauli("z"), model, bath, controls) + frequency = np.linspace(0.0, 3.0, 401) + heat = heat_current_spectrum(run.correlation, bath, frequency) + diagnostics = { + **run.diagnostics, + **run.metadata, + "connected_tail_amplitude": float(abs(run.correlation.connected[-1])), + } + accepted = bool( + diagnostics["fixed_point_residual"] <= 1e-3 + and diagnostics["trace_error"] <= 5e-3 + and diagnostics["hermiticity_error"] <= 5e-3 + and diagnostics["minimum_density_eigenvalue"] >= -5e-3 + and diagnostics["connected_tail_amplitude"] <= 5e-2 + ) + return { + "method": "uniform_tempo_single_spin_runtime_validation", + "converged": accepted, + "uniform_tempo_revision": UNIFORM_TEMPO_REVISION, + "model": asdict(model), + "bath": asdict(bath), + "controls": asdict(controls), + "diagnostics": diagnostics, + "phase_state": _complex_values(run.floquet_state), + "frequency": heat.frequencies.tolist(), + "continuous": heat.continuous.tolist(), + "delta_peaks": [asdict(item) for item in heat.delta_peaks], + } + + +def cross_backend_record(cache_directory: Path) -> dict[str, Any]: + common = dict( + j=0.25, + sector="odd", + steps_per_period=60, + phase_samples=3, + delay_periods=1, + frequency_points=401, + ) + uniform_point = N3HeatPoint( + **common, + backend="uniform_tempo", + epsrel=1e-6, + uniform_low_rank_svd=True, + uniform_truncation="abs", + uniform_cap_rank=5_000, + uniform_max_rank=10_000, + ) + oqupy_point = N3HeatPoint( + **common, + backend="oqupy", + memory_steps=3, + epsrel=1e-5, + steady_periods=20, + ) + cache = ConvergenceCache(cache_directory) + uniform = run_n3_heat_point(uniform_point, cache) + oqupy = run_n3_heat_point(oqupy_point, cache, commit="oqupy-cross-validation-v1") + uniform_grid = np.asarray(uniform["frequency"], dtype=float) + oqupy_grid = np.asarray(oqupy["frequency"], dtype=float) + uniform_delay = np.asarray(uniform["correlation"]["delay"], dtype=float) + oqupy_delay = np.asarray(oqupy["correlation"]["delay"], dtype=float) + metrics = { + "phase_state_frobenius": state_residual( + _complex_array(oqupy["phase_state"]), + _complex_array(uniform["phase_state"]), + ), + "connected_correlation_relative_l1": curve_residual( + oqupy_delay, + _complex_array(oqupy["correlation"]["connected"]), + uniform_delay, + _complex_array(uniform["correlation"]["connected"]), + ), + "continuous_heat_relative_l1": curve_residual( + oqupy_grid, + np.asarray(oqupy["continuous"], dtype=float), + uniform_grid, + np.asarray(uniform["continuous"], dtype=float), + ), + } + reference = prepare_n3_sector(uniform_point) + target = prepare_n3_sector(N3HeatPoint(**{**asdict(uniform_point), "j": 1.0})) + return { + "method": "coarse_uniform_tempo_vs_oqupy_cross_validation", + "complete": True, + "converged": bool( + uniform.get("complete") + and oqupy.get("complete") + and all(np.isfinite(value) for value in metrics.values()) + ), + "interpretation": ("independent coarse-backend diagnostic; not a convergence refinement"), + "uniform_fingerprint": uniform["fingerprint"], + "oqupy_fingerprint": oqupy["fingerprint"], + "projected_odd_j_invariance": { + "h0_frobenius_residual": float(np.linalg.norm(reference.h0 - target.h0)), + "coupling_frobenius_residual": float( + np.linalg.norm(reference.coupling - target.coupling) + ), + }, + "metrics": metrics, + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--output", type=Path, default=Path("results/validation")) + parser.add_argument( + "--cache", + type=Path, + default=Path("results/cache/uniform_tempo"), + ) + arguments = parser.parse_args() + arguments.output.mkdir(parents=True, exist_ok=True) + single = single_spin_record(arguments.cache) + atomic_write_result( + arguments.output / "uniform_tempo_single_spin.json", + single, + ) + cross = cross_backend_record(arguments.cache) + atomic_write_result( + arguments.output / "uniform_tempo_oqupy_crosscheck.json", + cross, + ) + return 0 if single["converged"] and cross["converged"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/__init__.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/__init__.py new file mode 100644 index 000000000..04a1f16ea --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/__init__.py @@ -0,0 +1,6 @@ +"""Few-body Floquet heat-spectroscopy research tools.""" + +from .config import BathConfig, ModelConfig, NumericsConfig, RunConfig + +__all__ = ["BathConfig", "ModelConfig", "NumericsConfig", "RunConfig"] +__version__ = "0.1.0" diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/adaptive.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/adaptive.py new file mode 100644 index 000000000..a9d034784 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/adaptive.py @@ -0,0 +1,702 @@ +"""Ordered, resumable refinement of PT-TEMPO numerical controls.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass, replace +from typing import Any, Literal, cast + +import numpy as np +from numpy.typing import NDArray +from scipy.integrate import trapezoid + +from .convergence import ConvergenceCache, state_residual +from .n3_heat import N3HeatPoint + +Parameter = Literal[ + "memory_steps", + "steps_per_period", + "epsrel", + "phase_samples", +] +Runner = Callable[[N3HeatPoint, ConvergenceCache | None], dict[str, Any]] + + +@dataclass(frozen=True) +class AdaptiveSchedule: + memory_steps: tuple[int, ...] = (3, 4, 5) + steps_per_period: tuple[int, ...] = (12, 16, 20) + epsrel: tuple[float, ...] = (1e-5, 3e-6) + state_threshold: float = 5e-2 + correlation_threshold: float = 5e-2 + heat_threshold: float = 5e-2 + phase_threshold: float = 1e-3 + trace_threshold: float = 5e-3 + + def __post_init__(self) -> None: + if any(value <= 0 for value in self.memory_steps + self.steps_per_period): + raise ValueError("integer refinement controls must be positive") + if any(not 0 < value < 1 for value in self.epsrel): + raise ValueError("epsrel refinements must lie between zero and one") + if tuple(sorted(self.memory_steps)) != self.memory_steps: + raise ValueError("memory_steps must increase") + if tuple(sorted(self.steps_per_period)) != self.steps_per_period: + raise ValueError("steps_per_period must increase") + if tuple(sorted(self.epsrel, reverse=True)) != self.epsrel: + raise ValueError("epsrel must decrease") + + +@dataclass(frozen=True) +class UniformAdaptiveSchedule: + """Refinement controls for a uniform infinite process tensor.""" + + steps_per_period: tuple[int, ...] = (60, 90, 120) + tolerances: tuple[float, ...] = (3e-7, 1e-7, 3e-8) + phase_samples: tuple[int, ...] = (3, 15) + state_threshold: float = 5e-2 + correlation_threshold: float = 8e-2 + heat_threshold: float = 8e-2 + phase_threshold: float = 1e-3 + trace_threshold: float = 5e-3 + hermiticity_threshold: float = 5e-3 + + def __post_init__(self) -> None: + if any(value < 2 for value in self.steps_per_period): + raise ValueError("steps_per_period values must be at least two") + if any(not 0 < value < 1 for value in self.tolerances): + raise ValueError("tolerances must lie between zero and one") + if any(value < 2 for value in self.phase_samples): + raise ValueError("phase_samples values must be at least two") + if tuple(sorted(self.steps_per_period)) != self.steps_per_period: + raise ValueError("steps_per_period must increase") + if tuple(sorted(self.tolerances, reverse=True)) != self.tolerances: + raise ValueError("tolerances must decrease") + if tuple(sorted(self.phase_samples)) != self.phase_samples: + raise ValueError("phase_samples must increase") + if any( + steps % samples != 0 + for steps in self.steps_per_period + for samples in self.phase_samples + ): + raise ValueError( + "every phase_samples value must divide every timestep refinement" + ) + + +@dataclass(frozen=True) +class RefinementEvidence: + parameter: Parameter + coarse_fingerprint: str + refined_fingerprint: str + coarse_value: float + refined_value: float + state_residual: float + correlation_residual: float + heat_residual: float + passed: bool + coarse_bond_dimension: int | None = None + refined_bond_dimension: int | None = None + coarse_steps_per_period: int | None = None + refined_steps_per_period: int | None = None + coarse_tolerance: float | None = None + refined_tolerance: float | None = None + coarse_phase_samples: int | None = None + refined_phase_samples: int | None = None + + +@dataclass(frozen=True) +class AdaptiveResult: + converged: bool + status: Literal["converged", "resource_ceiling", "backend_failure"] + final_point: N3HeatPoint + final_result: dict[str, Any] + evidence: tuple[RefinementEvidence, ...] + failed_parameter: Parameter | None = None + + +def _complex_array(value: dict[str, Any]) -> NDArray[np.complex128]: + return cast( + NDArray[np.complex128], + np.asarray(value["real"], dtype=float) + + 1j * np.asarray(value["imag"], dtype=float), + ) + + +def _aligned_residual( + candidate_grid: NDArray[np.float64], + candidate: NDArray[np.complex128] | NDArray[np.float64], + reference_grid: NDArray[np.float64], + reference: NDArray[np.complex128] | NDArray[np.float64], +) -> float: + """Compare curves on the coarser grid over their common domain.""" + lower = max(float(candidate_grid[0]), float(reference_grid[0])) + upper = min(float(candidate_grid[-1]), float(reference_grid[-1])) + common = candidate_grid[ + (candidate_grid >= lower - 1e-14) & (candidate_grid <= upper + 1e-14) + ] + if len(common) < 2: + raise ValueError("curve grids do not share a usable interval") + candidate_common = np.interp(common, candidate_grid, np.real(candidate)) + 1j * np.interp( + common, candidate_grid, np.imag(candidate) + ) + reference_common = np.interp(common, reference_grid, np.real(reference)) + 1j * np.interp( + common, reference_grid, np.imag(reference) + ) + numerator = float(trapezoid(abs(candidate_common - reference_common), common)) + denominator = float(trapezoid(abs(reference_common), common)) + 1e-15 + return numerator / denominator + + +def _residuals( + coarse: dict[str, Any], refined: dict[str, Any] +) -> tuple[float, float, float]: + state = state_residual( + _complex_array(coarse["phase_state"]), + _complex_array(refined["phase_state"]), + ) + coarse_correlation = _complex_array(coarse["correlation"]["connected"]) + refined_correlation = _complex_array(refined["correlation"]["connected"]) + correlation = _aligned_residual( + np.asarray(coarse["correlation"]["delay"], dtype=float), + coarse_correlation, + np.asarray(refined["correlation"]["delay"], dtype=float), + refined_correlation, + ) + heat = _aligned_residual( + np.asarray(coarse["frequency"], dtype=float), + np.asarray(coarse["continuous"], dtype=float), + np.asarray(refined["frequency"], dtype=float), + np.asarray(refined["continuous"], dtype=float), + ) + return state, correlation, heat + + +def _refinement_values( + point: N3HeatPoint, schedule: AdaptiveSchedule, parameter: Parameter +) -> tuple[int | float, ...]: + if parameter == "memory_steps": + values: tuple[int | float, ...] = schedule.memory_steps + current: int | float = point.memory_steps + elif parameter == "steps_per_period": + values = schedule.steps_per_period + current = point.steps_per_period + else: + values = schedule.epsrel + current = point.epsrel + if current in values: + return values[values.index(current) :] + if parameter != "epsrel": + return current, *tuple(value for value in values if value > current) + return current, *tuple(value for value in values if value < current) + + +def _replace_parameter( + point: N3HeatPoint, parameter: Parameter, value: int | float +) -> N3HeatPoint: + if parameter == "memory_steps": + return replace(point, memory_steps=int(value)) + if parameter == "steps_per_period": + steps = int(value) + memory = max( + point.memory_steps, + int(round(point.memory_steps * steps / point.steps_per_period)), + ) + return replace(point, steps_per_period=steps, memory_steps=memory) + return replace(point, epsrel=float(value)) + + +def run_adaptive( + point: N3HeatPoint, + schedule: AdaptiveSchedule, + runner: Runner, + cache: ConvergenceCache | None, +) -> AdaptiveResult: + """Refine memory, timestep, and SVD tolerance in that order.""" + current_point = point + current_result = runner(current_point, cache) + evidence: list[RefinementEvidence] = [] + if not bool(current_result.get("converged", False)): + return AdaptiveResult( + False, "backend_failure", current_point, current_result, tuple(evidence) + ) + + for parameter in ("memory_steps", "steps_per_period", "epsrel"): + typed_parameter: Parameter = parameter + values = _refinement_values(current_point, schedule, typed_parameter) + if len(values) < 2: + return AdaptiveResult( + False, + "resource_ceiling", + current_point, + current_result, + tuple(evidence), + typed_parameter, + ) + parameter_passed = False + for value in values[1:]: + refined_point = _replace_parameter(current_point, typed_parameter, value) + refined_result = runner(refined_point, cache) + if not bool(refined_result.get("converged", False)): + return AdaptiveResult( + False, + "backend_failure", + refined_point, + refined_result, + tuple(evidence), + typed_parameter, + ) + state, correlation, heat = _residuals(current_result, refined_result) + passed = ( + state <= schedule.state_threshold + and correlation <= schedule.correlation_threshold + and heat <= schedule.heat_threshold + ) + evidence.append( + RefinementEvidence( + typed_parameter, + str(current_result["fingerprint"]), + str(refined_result["fingerprint"]), + float(getattr(current_point, typed_parameter)), + float(value), + state, + correlation, + heat, + passed, + ) + ) + current_point = refined_point + current_result = refined_result + if passed: + parameter_passed = True + break + if not parameter_passed: + return AdaptiveResult( + False, + "resource_ceiling", + current_point, + current_result, + tuple(evidence), + typed_parameter, + ) + + diagnostics = current_result.get("diagnostics", {}) + final_checks = ( + float(diagnostics.get("phase_residual", np.inf)) + <= schedule.phase_threshold + and float(diagnostics.get("trace_error", np.inf)) + <= schedule.trace_threshold + and float(diagnostics.get("minimum_density_eigenvalue", -np.inf)) >= -5e-3 + and float(diagnostics.get("connected_tail_amplitude", np.inf)) <= 5e-2 + ) + return AdaptiveResult( + final_checks, + "converged" if final_checks else "backend_failure", + current_point, + current_result, + tuple(evidence), + ) + + +def _schedule_tail( + current: int | float, + values: tuple[int, ...] | tuple[float, ...], + *, + increasing: bool, +) -> tuple[int | float, ...]: + if current in values: + return values[values.index(current) :] + if increasing: + return current, *tuple(value for value in values if value > current) + return current, *tuple(value for value in values if value < current) + + +def _uniform_refined_point( + point: N3HeatPoint, + parameter: Parameter, + value: int | float, +) -> N3HeatPoint: + if parameter == "steps_per_period": + return replace(point, steps_per_period=int(value)) + if parameter == "phase_samples": + return replace(point, phase_samples=int(value)) + if parameter == "epsrel": + return replace(point, epsrel=float(value)) + raise ValueError(f"unsupported uniform refinement parameter {parameter}") + + +def _bond_dimension(result: dict[str, Any]) -> int | None: + value = result.get("diagnostics", {}).get("bond_dimension") + if value is None: + return None + return int(value) + + +def _uniform_physical_checks( + result: dict[str, Any], + schedule: UniformAdaptiveSchedule, +) -> bool: + diagnostics = result.get("diagnostics", {}) + return bool( + float(diagnostics.get("phase_residual", np.inf)) + <= schedule.phase_threshold + and float(diagnostics.get("trace_error", np.inf)) + <= schedule.trace_threshold + and float(diagnostics.get("hermiticity_error", np.inf)) + <= schedule.hermiticity_threshold + and float(diagnostics.get("minimum_density_eigenvalue", -np.inf)) >= -5e-3 + and float(diagnostics.get("connected_tail_amplitude", np.inf)) <= 5e-2 + ) + + +def run_uniform_adaptive( + point: N3HeatPoint, + schedule: UniformAdaptiveSchedule, + runner: Runner, + cache: ConvergenceCache | None, +) -> AdaptiveResult: + """Converge compression at every timestep, then refine phase quadrature. + + Compression and Trotter errors are coupled: a tolerance that is adequate on + one time grid need not be adequate on the next. Each timestep candidate is + therefore converged across the tolerance ladder before two adjacent + timestep candidates are compared. + """ + if point.backend != "uniform_tempo": + raise ValueError("uniform adaptive runner requires backend='uniform_tempo'") + current_phase_samples = point.phase_samples + if current_phase_samples is None: + raise ValueError("uniform adaptive runner requires explicit phase_samples") + + evidence: list[RefinementEvidence] = [] + timestep_values = _schedule_tail( + point.steps_per_period, + schedule.steps_per_period, + increasing=True, + ) + tolerance_values = _schedule_tail( + point.epsrel, + schedule.tolerances, + increasing=False, + ) + if len(timestep_values) < 2 or len(tolerance_values) < 2: + failed: Parameter = ( + "steps_per_period" if len(timestep_values) < 2 else "epsrel" + ) + initial_result = runner(point, cache) + return AdaptiveResult( + False, + "resource_ceiling", + point, + initial_result, + tuple(evidence), + failed, + ) + + previous_timestep_point: N3HeatPoint | None = None + previous_timestep_result: dict[str, Any] | None = None + previous_compression_passed = False + current_point = point + current_result: dict[str, Any] | None = None + timestep_passed = False + + for step_index, step_value in enumerate(timestep_values): + compression_point = replace( + point, + steps_per_period=int(step_value), + epsrel=float(tolerance_values[0]), + ) + compression_result = runner(compression_point, cache) + if not bool(compression_result.get("complete", False)): + return AdaptiveResult( + False, + "backend_failure", + compression_point, + compression_result, + tuple(evidence), + "epsrel", + ) + + compression_passed = False + for tolerance in tolerance_values[1:]: + refined_point = replace( + compression_point, + epsrel=float(tolerance), + ) + refined_result = runner(refined_point, cache) + if not bool(refined_result.get("complete", False)): + return AdaptiveResult( + False, + "backend_failure", + refined_point, + refined_result, + tuple(evidence), + "epsrel", + ) + state, correlation, heat = _residuals( + compression_result, + refined_result, + ) + passed = ( + state <= schedule.state_threshold + and correlation <= schedule.correlation_threshold + and heat <= schedule.heat_threshold + ) + evidence.append( + RefinementEvidence( + "epsrel", + str(compression_result["fingerprint"]), + str(refined_result["fingerprint"]), + float(compression_point.epsrel), + float(tolerance), + state, + correlation, + heat, + passed, + _bond_dimension(compression_result), + _bond_dimension(refined_result), + compression_point.steps_per_period, + refined_point.steps_per_period, + compression_point.epsrel, + refined_point.epsrel, + compression_point.phase_samples, + refined_point.phase_samples, + ) + ) + compression_point = refined_point + compression_result = refined_result + if passed: + compression_passed = True + break + current_point = compression_point + current_result = compression_result + if not compression_passed and step_index == len(timestep_values) - 1: + return AdaptiveResult( + False, + "resource_ceiling", + current_point, + current_result, + tuple(evidence), + "epsrel", + ) + + if previous_timestep_point is not None and previous_timestep_result is not None: + state, correlation, heat = _residuals( + previous_timestep_result, + current_result, + ) + passed = ( + previous_compression_passed + and compression_passed + and state <= schedule.state_threshold + and correlation <= schedule.correlation_threshold + and heat <= schedule.heat_threshold + and _uniform_physical_checks(current_result, schedule) + ) + evidence.append( + RefinementEvidence( + "steps_per_period", + str(previous_timestep_result["fingerprint"]), + str(current_result["fingerprint"]), + float(previous_timestep_point.steps_per_period), + float(current_point.steps_per_period), + state, + correlation, + heat, + passed, + _bond_dimension(previous_timestep_result), + _bond_dimension(current_result), + previous_timestep_point.steps_per_period, + current_point.steps_per_period, + previous_timestep_point.epsrel, + current_point.epsrel, + previous_timestep_point.phase_samples, + current_point.phase_samples, + ) + ) + if passed: + timestep_passed = True + break + previous_timestep_point = current_point + previous_timestep_result = current_result + previous_compression_passed = compression_passed + + if current_result is None: + raise RuntimeError("uniform adaptive refinement produced no result") + if not timestep_passed: + return AdaptiveResult( + False, + "resource_ceiling", + current_point, + current_result, + tuple(evidence), + "steps_per_period", + ) + + phase_values = _schedule_tail( + current_phase_samples, + schedule.phase_samples, + increasing=True, + ) + if len(phase_values) < 2: + return AdaptiveResult( + False, + "resource_ceiling", + current_point, + current_result, + tuple(evidence), + "phase_samples", + ) + phase_passed = False + for phase_value in phase_values[1:]: + refined_point = replace(current_point, phase_samples=int(phase_value)) + refined_result = runner(refined_point, cache) + if not bool(refined_result.get("complete", False)): + return AdaptiveResult( + False, + "backend_failure", + refined_point, + refined_result, + tuple(evidence), + "phase_samples", + ) + state, correlation, heat = _residuals(current_result, refined_result) + passed = ( + state <= schedule.state_threshold + and correlation <= schedule.correlation_threshold + and heat <= schedule.heat_threshold + and _uniform_physical_checks(refined_result, schedule) + ) + evidence.append( + RefinementEvidence( + "phase_samples", + str(current_result["fingerprint"]), + str(refined_result["fingerprint"]), + float(current_point.phase_samples or current_phase_samples), + float(phase_value), + state, + correlation, + heat, + passed, + _bond_dimension(current_result), + _bond_dimension(refined_result), + current_point.steps_per_period, + refined_point.steps_per_period, + current_point.epsrel, + refined_point.epsrel, + current_point.phase_samples, + refined_point.phase_samples, + ) + ) + current_point = refined_point + current_result = refined_result + if passed: + phase_passed = True + break + if not phase_passed: + return AdaptiveResult( + False, + "resource_ceiling", + current_point, + current_result, + tuple(evidence), + "phase_samples", + ) + + final_checks = _uniform_physical_checks(current_result, schedule) + return AdaptiveResult( + final_checks, + "converged" if final_checks else "backend_failure", + current_point, + current_result, + tuple(evidence), + ) + + +def run_uniform_compression_audit( + point: N3HeatPoint, + schedule: UniformAdaptiveSchedule, + runner: Runner, + cache: ConvergenceCache | None, +) -> AdaptiveResult: + """Converge only MPO compression and record a timestep resource ceiling. + + This is used for explicitly exploratory points whose next timestep grid is + outside the declared local budget. It never returns ``converged``. + """ + if point.backend != "uniform_tempo": + raise ValueError("uniform compression audit requires backend='uniform_tempo'") + tolerance_values = _schedule_tail( + point.epsrel, + schedule.tolerances, + increasing=False, + ) + current_point = point + current_result = runner(current_point, cache) + evidence: list[RefinementEvidence] = [] + if not bool(current_result.get("complete", False)): + return AdaptiveResult( + False, + "backend_failure", + current_point, + current_result, + tuple(evidence), + "epsrel", + ) + for tolerance in tolerance_values[1:]: + refined_point = replace(current_point, epsrel=float(tolerance)) + refined_result = runner(refined_point, cache) + if not bool(refined_result.get("complete", False)): + return AdaptiveResult( + False, + "backend_failure", + refined_point, + refined_result, + tuple(evidence), + "epsrel", + ) + state, correlation, heat = _residuals(current_result, refined_result) + passed = ( + state <= schedule.state_threshold + and correlation <= schedule.correlation_threshold + and heat <= schedule.heat_threshold + ) + evidence.append( + RefinementEvidence( + "epsrel", + str(current_result["fingerprint"]), + str(refined_result["fingerprint"]), + current_point.epsrel, + refined_point.epsrel, + state, + correlation, + heat, + passed, + _bond_dimension(current_result), + _bond_dimension(refined_result), + current_point.steps_per_period, + refined_point.steps_per_period, + current_point.epsrel, + refined_point.epsrel, + current_point.phase_samples, + refined_point.phase_samples, + ) + ) + current_point = refined_point + current_result = refined_result + if passed: + return AdaptiveResult( + False, + "resource_ceiling", + current_point, + current_result, + tuple(evidence), + "steps_per_period", + ) + return AdaptiveResult( + False, + "resource_ceiling", + current_point, + current_result, + tuple(evidence), + "epsrel", + ) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/__init__.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/__init__.py new file mode 100644 index 000000000..265614438 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/__init__.py @@ -0,0 +1,21 @@ +"""Open-system solver backends.""" + +from .base import OpenSystemResult +from .finite_memory import FiniteMemoryBackend +from .floquet_markov import FloquetMarkovBackend +from .pt_tempo import PtTempoBackend +from .uniform_tempo import ( + UniformTempoBackend, + UniformTempoControls, + UniformTempoResult, +) + +__all__ = [ + "FiniteMemoryBackend", + "FloquetMarkovBackend", + "OpenSystemResult", + "PtTempoBackend", + "UniformTempoBackend", + "UniformTempoControls", + "UniformTempoResult", +] diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/base.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/base.py new file mode 100644 index 000000000..8deb6b83d --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/base.py @@ -0,0 +1,20 @@ +"""Common, provenance-rich open-system result types.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +import numpy as np +from numpy.typing import NDArray + + +@dataclass(frozen=True) +class OpenSystemResult: + method: str + density_matrices: NDArray[np.complex128] + times: NDArray[np.float64] + converged: bool + diagnostics: dict[str, float] + metadata: dict[str, Any] + step_maps: NDArray[np.complex128] | None = None diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/finite_memory.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/finite_memory.py new file mode 100644 index 000000000..d6cea3b1e --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/finite_memory.py @@ -0,0 +1,136 @@ +"""Brute-force augmented-density-tensor influence-functional backend. + +This is a transparent finite-memory QUAPI implementation for few-level +validation. Its cost is O((d^2)^(K+1)); it intentionally refuses unsafe sizes. +""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import asdict + +import numpy as np +from scipy.linalg import expm + +from ..config import BathConfig +from ..influence import InfluenceCoefficients, discretize_influence +from ..operators import ComplexMatrix +from .base import OpenSystemResult + + +def _trace_from_vector(vector: np.ndarray, dimension: int) -> complex: + return complex(sum(vector[index * dimension + index] for index in range(dimension))) + + +class FiniteMemoryBackend: + """Finite-memory path sum in the coupling-operator eigenbasis.""" + + method = "finite_memory_if" + + def __init__(self, max_augmented_elements: int = 2_000_000): + self.max_augmented_elements = max_augmented_elements + + @staticmethod + def _influence_table( + eigenvalues: np.ndarray, coefficients: InfluenceCoefficients, depth: int + ) -> np.ndarray: + dimension = len(eigenvalues) + q = dimension**2 + pairs = [(plus, minus) for plus in range(dimension) for minus in range(dimension)] + shape = (q,) * (depth + 1) + table = np.ones(shape, dtype=np.complex128) + for new_index, (new_plus, new_minus) in enumerate(pairs): + delta = eigenvalues[new_plus] - eigenvalues[new_minus] + for indices in np.ndindex(*(q,) * depth): + exponent = 0.0j + for lag, history_index in enumerate(indices, start=1): + old_plus, old_minus = pairs[history_index] + eta = coefficients.values[lag] + exponent -= delta * ( + eta * eigenvalues[old_plus] - eta.conjugate() * eigenvalues[old_minus] + ) + eta0 = coefficients.values[0] + exponent -= delta * ( + eta0 * eigenvalues[new_plus] + - eta0.conjugate() * eigenvalues[new_minus] + ) + table[(new_index, *indices)] = np.exp(exponent) + return table + + def run( + self, + hamiltonian: Callable[[float], ComplexMatrix], + coupling: ComplexMatrix, + initial_density: ComplexMatrix, + bath: BathConfig, + dt: float, + steps: int, + memory_steps: int, + ) -> OpenSystemResult: + if steps < 1: + raise ValueError("steps must be positive") + eigenvalues, basis = np.linalg.eigh(coupling) + dimension = coupling.shape[0] + q = dimension**2 + augmented_size = q ** (memory_steps + 1) + if augmented_size > self.max_augmented_elements: + raise ValueError( + f"augmented tensor requires {augmented_size} elements; " + f"limit is {self.max_augmented_elements}" + ) + coefficients = discretize_influence(bath, dt, memory_steps) + rho = basis.conj().T @ initial_density @ basis + augmented = rho.reshape(q).copy() + histories = 1 + output = [initial_density.copy()] + maximum_trace_error = 0.0 + minimum_eigenvalue = float(np.min(np.linalg.eigvalsh(initial_density))) + + for step in range(steps): + midpoint = (step + 0.5) * dt + transformed_h = basis.conj().T @ hamiltonian(midpoint) @ basis + unitary = expm(-1j * transformed_h * dt) + propagator = np.einsum("ac,bd->abcd", unitary, unitary.conj()).reshape(q, q) + active_depth = min(histories, memory_steps) + table = self._influence_table(eigenvalues, coefficients, active_depth) + transition_shape = (q, q) + (1,) * (active_depth - 1) + expanded = propagator.reshape(transition_shape) * augmented[np.newaxis, ...] * table + if histories >= memory_steps: + augmented = expanded.sum(axis=-1) + else: + augmented = expanded + histories += 1 + reduced = augmented + while reduced.ndim > 1: + reduced = reduced.sum(axis=-1) + trace = _trace_from_vector(reduced, dimension) + maximum_trace_error = max(maximum_trace_error, abs(trace - 1)) + if abs(trace) > 1e-14: + augmented /= trace + reduced /= trace + rho_s = reduced.reshape(dimension, dimension) + rho_lab = basis @ rho_s @ basis.conj().T + rho_lab = (rho_lab + rho_lab.conj().T) / 2 + minimum_eigenvalue = min(minimum_eigenvalue, float(np.min(np.linalg.eigvalsh(rho_lab)))) + output.append(rho_lab) + + diagnostics = { + "trace_error": maximum_trace_error, + "minimum_density_eigenvalue": minimum_eigenvalue, + "quadrature_error": coefficients.quadrature_error, + "correlation_tail_bound": coefficients.tail_bound, + } + converged = maximum_trace_error < 1e-8 and minimum_eigenvalue > -1e-6 + return OpenSystemResult( + self.method, + np.asarray(output), + np.asarray(np.arange(steps + 1) * dt, dtype=np.float64), + converged, + diagnostics, + { + "bath": asdict(bath), + "dt": dt, + "memory_steps": memory_steps, + "approximation": "finite timestep and hard memory cutoff", + }, + ) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/floquet_markov.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/floquet_markov.py new file mode 100644 index 000000000..1f3e75d39 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/floquet_markov.py @@ -0,0 +1,192 @@ +"""Secular Floquet-Markov population solver.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +import numpy as np +from scipy.linalg import expm + +from ..bath import bose_occupation, ohmic_spectral_density +from ..config import BathConfig +from ..floquet import FloquetSolution, micromotion, solve_floquet +from ..operators import ComplexMatrix +from .base import OpenSystemResult + + +@dataclass(frozen=True) +class FloquetRates: + matrix: np.ndarray + fourier_elements: np.ndarray + + +def _bath_rate(frequency: float, bath: BathConfig) -> float: + if abs(frequency) < 1e-14: + return 0.0 + positive = abs(frequency) + density = float(ohmic_spectral_density(positive, bath)) + occupation = bose_occupation(positive, bath.temperature) + return 2 * np.pi * density * (occupation + (1 if frequency > 0 else 0)) + + +class FloquetMarkovBackend: + method = "floquet_markov" + + def rates( + self, + solution: FloquetSolution, + coupling: ComplexMatrix, + bath: BathConfig, + harmonic_cutoff: int, + ) -> FloquetRates: + cumulative = micromotion(solution)[:-1] + steps = len(cumulative) + omega_d = 2 * np.pi / solution.period + times = np.arange(steps) * solution.period / steps + dimension = coupling.shape[0] + harmonics = np.arange(-harmonic_cutoff, harmonic_cutoff + 1) + elements = np.zeros( + (len(harmonics), dimension, dimension), dtype=np.complex128 + ) + for time, propagator in zip(times, cumulative, strict=True): + periodic_modes = ( + propagator + @ solution.modes + @ np.diag(np.exp(1j * solution.quasienergies * time)) + ) + instantaneous = periodic_modes.conj().T @ coupling @ periodic_modes + elements += ( + np.exp(-1j * harmonics[:, None, None] * omega_d * time) + * instantaneous[None, :, :] + / steps + ) + + rates = np.zeros((dimension, dimension), dtype=float) + for target in range(dimension): + for source in range(dimension): + if target == source: + continue + for h_index, harmonic in enumerate(harmonics): + emitted = ( + solution.quasienergies[source] + - solution.quasienergies[target] + + harmonic * omega_d + ) + rates[target, source] += _bath_rate(emitted, bath) * abs( + elements[h_index, target, source] + ) ** 2 + for source in range(dimension): + rates[source, source] = -np.sum(rates[:, source]) + return FloquetRates(rates, elements) + + def run( + self, + hamiltonian: Callable[[float], ComplexMatrix], + coupling: ComplexMatrix, + bath: BathConfig, + period: float, + steps: int, + harmonic_cutoff: int, + ) -> OpenSystemResult: + solution = solve_floquet(hamiltonian, period, steps) + rate_data = self.rates(solution, coupling, bath, harmonic_cutoff) + matrix = rate_data.matrix.copy() + rhs = np.zeros(matrix.shape[0]) + matrix[-1, :] = 1 + rhs[-1] = 1 + populations, *_ = np.linalg.lstsq(matrix, rhs, rcond=None) + populations = np.real_if_close(populations).real + populations = np.clip(populations, 0, None) + populations /= populations.sum() + dimension = coupling.shape[0] + identity = np.eye(dimension, dtype=np.complex128) + omega_d = 2 * np.pi / period + harmonics = np.arange(-harmonic_cutoff, harmonic_cutoff + 1) + cumulative = micromotion(solution)[:-1] + dt = period / steps + step_maps: list[ComplexMatrix] = [] + for index, propagator in enumerate(cumulative): + time = index * dt + modes = ( + propagator + @ solution.modes + @ np.diag(np.exp(1j * solution.quasienergies * time)) + ) + h = hamiltonian((index + 0.5) * dt) + liouvillian = -1j * ( + np.kron(identity, h) - np.kron(h.T, identity) + ) + for target in range(dimension): + for source in range(dimension): + if target == source: + continue + basis_jump = np.outer(modes[:, target], modes[:, source].conj()) + for harmonic_index, harmonic in enumerate(harmonics): + emitted = ( + solution.quasienergies[source] + - solution.quasienergies[target] + + harmonic * omega_d + ) + gamma = _bath_rate(emitted, bath) + amplitude = rate_data.fourier_elements[ + harmonic_index, target, source + ] + if gamma == 0 or abs(amplitude) < 1e-14: + continue + jump = np.sqrt(gamma) * amplitude * basis_jump + product = jump.conj().T @ jump + liouvillian += ( + np.kron(jump.conj(), jump) + - 0.5 * np.kron(identity, product) + - 0.5 * np.kron(product.T, identity) + ) + step_maps.append(expm(liouvillian * dt)) + + period_map = np.eye(dimension**2, dtype=np.complex128) + for step_map in step_maps: + period_map = step_map @ period_map + values, vectors = np.linalg.eig(period_map) + steady_index = int(np.argmin(abs(values - 1))) + steady_vector = vectors[:, steady_index] + steady_density = steady_vector.reshape((dimension, dimension), order="F") + steady_density = (steady_density + steady_density.conj().T) / 2 + steady_density /= np.trace(steady_density) + densities = [steady_density] + vector = steady_density.reshape(dimension**2, order="F") + for step_map in step_maps: + vector = step_map @ vector + density = vector.reshape((dimension, dimension), order="F") + density = (density + density.conj().T) / 2 + density /= np.trace(density) + densities.append(density) + vector = density.reshape(dimension**2, order="F") + rate_residual = float(np.linalg.norm(rate_data.matrix @ populations)) + map_residual = float( + np.linalg.norm( + period_map @ steady_density.reshape(dimension**2, order="F") + - steady_density.reshape(dimension**2, order="F") + ) + ) + return OpenSystemResult( + self.method, + np.asarray(densities), + np.linspace(0, period, steps + 1), + map_residual < 1e-8, + { + "rate_residual": rate_residual, + "period_map_residual": map_residual, + "trace_error": float( + max(abs(np.trace(density) - 1) for density in densities) + ), + "minimum_population": float( + min(np.min(np.linalg.eigvalsh(density)) for density in densities) + ), + }, + { + "approximation": "Born-Markov, Floquet and full secular approximations", + "harmonic_cutoff": harmonic_cutoff, + "populations": populations.tolist(), + }, + np.asarray(step_maps), + ) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/pt_tempo.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/pt_tempo.py new file mode 100644 index 000000000..b9371693c --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/pt_tempo.py @@ -0,0 +1,180 @@ +"""OQuPy PT-TEMPO backend for controlled non-Markovian calculations.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +import numpy as np + +from ..config import BathConfig +from ..operators import ComplexMatrix +from .base import OpenSystemResult + + +@dataclass(frozen=True) +class PtTempoResult: + result: OpenSystemResult + process_tensor: Any + system: Any + bath: Any + initial_density: ComplexMatrix + + +class PtTempoBackend: + """Numerically controlled PT-TEMPO process-tensor wrapper. + + OQuPy uses ``J(w)=2*alpha*w*exp(-w/wc)``. This wrapper passes half of the + project's alpha so both packages implement the same spectral density. + """ + + method = "pt_tempo" + + @staticmethod + def _oqupy() -> Any: + try: + import oqupy + except ImportError as exc: + raise RuntimeError( + "PT-TEMPO requires the 'nonmarkov' optional dependencies" + ) from exc + return oqupy + + def run( + self, + hamiltonian: Any, + coupling: ComplexMatrix, + initial_density: ComplexMatrix, + bath_config: BathConfig, + dt: float, + steps: int, + memory_steps: int, + epsrel: float, + ) -> PtTempoResult: + if dt <= 0 or steps < 1 or memory_steps < 1 or not 0 < epsrel < 1: + raise ValueError("invalid PT-TEMPO numerical parameters") + oqupy = self._oqupy() + correlations = oqupy.PowerLawSD( + alpha=bath_config.alpha / 2, + zeta=1, + cutoff=bath_config.cutoff, + cutoff_type="exponential", + temperature=bath_config.temperature, + ) + bath = oqupy.Bath(coupling, correlations) + parameters = oqupy.TempoParameters( + dt=dt, + epsrel=epsrel, + dkmax=memory_steps, + ) + process_tensor = oqupy.pt_tempo_compute( + bath=bath, + start_time=0.0, + end_time=steps * dt, + parameters=parameters, + unique=True, + progress_type="silent", + ) + system = oqupy.TimeDependentSystem(hamiltonian) + dynamics = oqupy.compute_dynamics( + system=system, + initial_state=initial_density, + process_tensor=process_tensor, + progress_type="silent", + ) + states = np.asarray(dynamics.states, dtype=np.complex128) + traces = np.trace(states, axis1=1, axis2=2) + trace_error = float(np.max(abs(traces - 1))) + minimum_eigenvalue = float( + min(np.min(np.linalg.eigvalsh((state + state.conj().T) / 2)) for state in states) + ) + bond_dimensions = process_tensor.get_bond_dimensions() + result = OpenSystemResult( + self.method, + states, + np.asarray(dynamics.times, dtype=np.float64), + trace_error < 5e-3 and minimum_eigenvalue > -5e-3, + { + "trace_error": trace_error, + "minimum_density_eigenvalue": minimum_eigenvalue, + "maximum_bond_dimension": float(np.max(bond_dimensions)), + }, + { + "approximation": "PT-TEMPO: finite dt, dkmax and SVD epsrel", + "dt": dt, + "memory_steps": memory_steps, + "epsrel": epsrel, + "spectral_density_convention": ( + "OQuPy alpha divided by two to match J=alpha*w*exp(-w/wc)" + ), + }, + ) + return PtTempoResult(result, process_tensor, system, bath, initial_density) + + def period_averaged_correlation( + self, + run: PtTempoResult, + operator: ComplexMatrix, + phase_start: int, + period_steps: int, + delay_steps: int, + drive_frequency: float, + phase_offsets: list[int] | None = None, + ) -> Any: + """Calculate exact process-tensor insertions and average drive phases.""" + from ..correlations import CorrelationResult, coherent_decomposition + + oqupy = self._oqupy() + offsets = list(range(period_steps)) if phase_offsets is None else phase_offsets + if ( + len(offsets) < 2 + or len(set(offsets)) != len(offsets) + or min(offsets) < 0 + or max(offsets) >= period_steps + ): + raise ValueError("phase offsets must be distinct indices in one period") + initial_indices = [phase_start + offset for offset in offsets] + final_indices = list( + range(phase_start, phase_start + period_steps + delay_steps) + ) + _, matrix = oqupy.compute_correlations( + system=run.system, + process_tensor=run.process_tensor, + operator_a=operator, + operator_b=operator, + times_a=initial_indices, + times_b=final_indices, + time_order="ordered", + initial_state=run.initial_density, + progress_type="silent", + ) + total = np.empty(delay_steps + 1, dtype=np.complex128) + for delay in range(delay_steps + 1): + values = [ + matrix[row, offset + delay] + for row, offset in enumerate(offsets) + ] + total[delay] = np.mean(values) + dt = float(run.result.times[1] - run.result.times[0]) + delays = np.arange(delay_steps + 1, dtype=np.float64) * dt + phase_states = run.result.density_matrices[ + [phase_start + offset for offset in offsets] + ] + one_point = np.real( + np.einsum("ij,tji->t", operator, phase_states, optimize=True) + ) + coherent, peaks = coherent_decomposition(one_point, drive_frequency, delays) + return CorrelationResult( + delays, + total, + total - coherent, + coherent, + peaks, + "pt_tempo_multitime", + { + "period_steps": float(period_steps), + "phase_samples": float(len(offsets)), + "dt": dt, + "phase_start": float(phase_start), + }, + ) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/uniform_tempo.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/uniform_tempo.py new file mode 100644 index 000000000..8bb09222d --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/backends/uniform_tempo.py @@ -0,0 +1,472 @@ +"""Pinned UniformTEMPO.jl backend for periodic non-Markovian correlations.""" + +from __future__ import annotations + +import hashlib +import json +import shutil +import subprocess +import tempfile +from dataclasses import asdict, dataclass, field +from pathlib import Path +from typing import Any, Literal, cast + +import numpy as np +from numpy.typing import NDArray + +from ..config import BathConfig, ModelConfig +from ..correlations import CorrelationResult, coherent_decomposition +from ..operators import ComplexMatrix + +METHOD = "uniform_tempo_floquet_multitime" +UNIFORM_TEMPO_REVISION = "b76a018c32e5415989761d902b1b0e95f1a337da" + + +@dataclass(frozen=True) +class UniformTempoControls: + """Complete numerical controls passed to the Julia runner.""" + + steps_per_period: int + tolerance: float + phase_samples: int + delay_periods: int + auto_nc: bool = True + memory_cutoff: int = 100_000 + low_rank_svd: bool = False + truncation: Literal["rel", "abs"] = "rel" + cap_rank: int = 100_000 + max_rank: int = 100_000 + pole_count: int = 0 + pole_tolerance: float = 1e-10 + pole_maxiter: int = 2_000 + + def __post_init__(self) -> None: + if self.steps_per_period < 2: + raise ValueError("steps_per_period must be at least two") + if not 0 < self.tolerance < 1: + raise ValueError("tolerance must lie between zero and one") + if ( + self.phase_samples < 2 + or self.phase_samples > self.steps_per_period + or self.steps_per_period % self.phase_samples != 0 + ): + raise ValueError("phase_samples must divide steps_per_period") + if self.delay_periods < 1: + raise ValueError("delay_periods must be positive") + if self.memory_cutoff < 1: + raise ValueError("memory_cutoff must be positive") + if self.truncation not in ("rel", "abs"): + raise ValueError("truncation must be 'rel' or 'abs'") + if self.cap_rank < 1 or self.max_rank < self.cap_rank: + raise ValueError("invalid rank limits") + if isinstance(self.pole_count, bool) or self.pole_count < 0: + raise ValueError("pole_count must be nonnegative") + if self.pole_tolerance <= 0: + raise ValueError("pole_tolerance must be positive") + if isinstance(self.pole_maxiter, bool) or self.pole_maxiter < 1: + raise ValueError("pole_maxiter must be positive") + + @property + def phase_offsets(self) -> tuple[int, ...]: + stride = self.steps_per_period // self.phase_samples + return tuple(range(0, self.steps_per_period, stride)) + + @property + def delay_steps(self) -> int: + return self.delay_periods * self.steps_per_period + + +@dataclass(frozen=True) +class UniformTempoResult: + method: str + floquet_state: ComplexMatrix + phase_states: NDArray[np.complex128] + correlation: CorrelationResult + diagnostics: dict[str, float] + metadata: dict[str, float | int | str] + transfer_eigenvalues: NDArray[np.complex128] = field( + default_factory=lambda: np.empty(0, dtype=np.complex128) + ) + transfer_eigenpair_residuals: NDArray[np.float64] = field( + default_factory=lambda: np.empty(0, dtype=np.float64) + ) + + +def _encode_complex(values: NDArray[np.complex128] | ComplexMatrix) -> dict[str, Any]: + array = np.asarray(values, dtype=np.complex128) + return { + "real": np.real(array).astype(float).tolist(), + "imag": np.imag(array).astype(float).tolist(), + } + + +def _tensor_cache_key( + coupling: ComplexMatrix, + model: ModelConfig, + bath: BathConfig, + controls: UniformTempoControls, +) -> str: + """Fingerprint every input that changes the uniform process tensor.""" + payload = { + "schema": "uniform-process-tensor-v1", + "uniform_tempo_revision": UNIFORM_TEMPO_REVISION, + "coupling": _encode_complex(np.asarray(coupling, dtype=np.complex128)), + "bath": asdict(bath), + "dt": model.period / controls.steps_per_period, + "tolerance": controls.tolerance, + "auto_nc": controls.auto_nc, + "memory_cutoff": controls.memory_cutoff, + "low_rank_svd": controls.low_rank_svd, + "truncation": controls.truncation, + "cap_rank": controls.cap_rank, + "max_rank": controls.max_rank, + } + encoded = json.dumps( + payload, + sort_keys=True, + separators=(",", ":"), + allow_nan=False, + ).encode("utf-8") + return hashlib.sha256(encoded).hexdigest() + + +def _required(payload: dict[str, Any], key: str, context: str = "payload") -> Any: + if key not in payload: + raise ValueError(f"{context} is missing required field {key!r}") + return payload[key] + + +def _decode_complex( + payload: dict[str, Any], + *, + label: str, + allow_empty: bool = False, +) -> NDArray[np.complex128]: + try: + shape = tuple(int(value) for value in payload["shape"]) + real = np.asarray(payload["real"], dtype=float) + imag = np.asarray(payload["imag"], dtype=float) + except (KeyError, TypeError, ValueError) as exc: + raise ValueError(f"{label} has an invalid complex-array schema") from exc + expected = int(np.prod(shape, dtype=np.int64)) + if ( + expected < 0 + or (expected == 0 and not allow_empty) + or real.size != expected + or imag.size != expected + ): + raise ValueError(f"{label} data do not match the declared shape") + values = real.reshape(-1) + 1j * imag.reshape(-1) + array = values.reshape(shape, order="F") + if not np.all(np.isfinite(array)): + raise ValueError(f"{label} contains non-finite values") + return cast(NDArray[np.complex128], array) + + +class UniformTempoBackend: + """Subprocess boundary around the pinned Julia implementation.""" + + def __init__( + self, + *, + command_prefix: tuple[str, ...] | None = None, + timeout_seconds: float = 86_400.0, + tensor_cache_directory: Path | None = None, + ) -> None: + if timeout_seconds <= 0: + raise ValueError("timeout_seconds must be positive") + root = Path(__file__).resolve().parents[3] + if command_prefix is None: + julia = shutil.which("julia") + if julia is None: + raise RuntimeError("UniformTEMPO backend requires Julia on PATH") + command_prefix = ( + julia, + f"--project={root / 'julia'}", + str(root / "julia" / "run_uniform_tempo.jl"), + ) + if not command_prefix: + raise ValueError("command_prefix must not be empty") + self._command_prefix = tuple(command_prefix) + self._timeout_seconds = timeout_seconds + self._tensor_cache_directory = ( + None + if tensor_cache_directory is None + else Path(tensor_cache_directory).resolve() + ) + + def run_periodic( + self, + h0: ComplexMatrix, + coupling: ComplexMatrix, + model: ModelConfig, + bath: BathConfig, + controls: UniformTempoControls, + *, + drive_operator: ComplexMatrix | None = None, + ) -> UniformTempoResult: + """Compute the Floquet state and phase-averaged two-time correlation.""" + h0_array = np.asarray(h0, dtype=np.complex128) + coupling_array = np.asarray(coupling, dtype=np.complex128) + drive_array = ( + coupling_array + if drive_operator is None + else np.asarray(drive_operator, dtype=np.complex128) + ) + if h0_array.ndim != 2 or h0_array.shape[0] != h0_array.shape[1]: + raise ValueError("h0 must be a square matrix") + if coupling_array.shape != h0_array.shape: + raise ValueError("coupling must match h0") + if drive_array.shape != h0_array.shape: + raise ValueError("drive_operator must match h0") + if not all( + np.all(np.isfinite(array)) + for array in (h0_array, coupling_array, drive_array) + ): + raise ValueError("system operators must contain finite values") + if not np.allclose(h0_array, h0_array.conj().T, atol=1e-10): + raise ValueError("h0 must be Hermitian") + if not np.allclose(coupling_array, coupling_array.conj().T, atol=1e-10): + raise ValueError("coupling must be Hermitian") + if not np.allclose(drive_array, drive_array.conj().T, atol=1e-10): + raise ValueError("drive_operator must be Hermitian") + + request: dict[str, Any] = { + "h0": _encode_complex(h0_array), + "coupling": _encode_complex(coupling_array), + "drive": _encode_complex(drive_array), + "model": asdict(model), + "bath": asdict(bath), + "controls": { + "steps_per_period": controls.steps_per_period, + "tolerance": controls.tolerance, + "phase_offsets": list(controls.phase_offsets), + "delay_steps": controls.delay_steps, + "auto_nc": controls.auto_nc, + "memory_cutoff": controls.memory_cutoff, + "low_rank_svd": controls.low_rank_svd, + "truncation": controls.truncation, + "cap_rank": controls.cap_rank, + "max_rank": controls.max_rank, + "pole_count": controls.pole_count, + "pole_tolerance": controls.pole_tolerance, + "pole_maxiter": controls.pole_maxiter, + }, + } + if self._tensor_cache_directory is not None: + tensor_key = _tensor_cache_key( + coupling_array, + model, + bath, + controls, + ) + self._tensor_cache_directory.mkdir(parents=True, exist_ok=True) + request["controls"]["process_tensor_cache_key"] = tensor_key + request["controls"]["process_tensor_cache_path"] = str( + self._tensor_cache_directory / f"{tensor_key}.jls" + ) + with tempfile.TemporaryDirectory(prefix="uniform-tempo-") as temporary: + temporary_path = Path(temporary) + input_path = temporary_path / "input.json" + output_path = temporary_path / "output.json" + input_path.write_text( + json.dumps(request, sort_keys=True), + encoding="utf-8", + ) + try: + completed = subprocess.run( + [*self._command_prefix, str(input_path), str(output_path)], + capture_output=True, + text=True, + timeout=self._timeout_seconds, + check=False, + ) + except subprocess.TimeoutExpired as exc: + raise RuntimeError( + f"UniformTEMPO runner exceeded {self._timeout_seconds:g} seconds" + ) from exc + if completed.returncode != 0: + detail = completed.stderr.strip() or completed.stdout.strip() + raise RuntimeError( + f"UniformTEMPO runner failed with exit code " + f"{completed.returncode}: {detail}" + ) + if not output_path.is_file(): + raise RuntimeError("UniformTEMPO runner did not create output JSON") + try: + payload = cast( + dict[str, Any], + json.loads(output_path.read_text(encoding="utf-8")), + ) + except (json.JSONDecodeError, OSError) as exc: + raise ValueError("UniformTEMPO runner returned malformed JSON") from exc + return self._validate_payload(payload, h0_array.shape[0], model, controls) + + @staticmethod + def _validate_payload( + payload: dict[str, Any], + dimension: int, + model: ModelConfig, + controls: UniformTempoControls, + ) -> UniformTempoResult: + method = str(_required(payload, "method")) + if method != METHOD: + raise ValueError(f"unexpected UniformTEMPO method label {method!r}") + for provenance_key in ( + "julia_version", + "uniform_tempo_revision", + "manifest_sha256", + ): + if provenance_key not in payload or not str(payload[provenance_key]): + raise ValueError("UniformTEMPO payload is missing provenance") + manifest_hash = str(payload["manifest_sha256"]) + if len(manifest_hash) != 64: + raise ValueError("UniformTEMPO manifest provenance is invalid") + + period_steps = int(_required(payload, "period_steps")) + if period_steps != controls.steps_per_period: + raise ValueError("UniformTEMPO period grid does not match the request") + phase_offsets = tuple(int(value) for value in _required(payload, "phase_offsets")) + if phase_offsets != controls.phase_offsets: + raise ValueError("UniformTEMPO phase grid does not match the request") + dt = float(_required(payload, "dt")) + if not np.isclose( + dt, + model.period / controls.steps_per_period, + rtol=1e-11, + atol=1e-13, + ): + raise ValueError("UniformTEMPO timestep does not match the model period") + + floquet_state_raw = _decode_complex( + cast(dict[str, Any], _required(payload, "floquet_state")), + label="floquet_state", + ) + if floquet_state_raw.shape != (dimension, dimension): + raise ValueError("floquet_state has the wrong shape") + phase_states_raw = _decode_complex( + cast(dict[str, Any], _required(payload, "phase_states")), + label="phase_states", + ) + expected_phase_shape = ( + dimension, + dimension, + controls.phase_samples, + ) + if phase_states_raw.shape != expected_phase_shape: + raise ValueError("phase_states have the wrong shape") + phase_states = np.moveaxis(phase_states_raw, 2, 0) + + delay = np.asarray(_required(payload, "delay"), dtype=float) + if delay.shape != (controls.delay_steps + 1,) or not np.all(np.isfinite(delay)): + raise ValueError("delay grid has the wrong shape") + if not np.allclose(delay, np.arange(len(delay)) * dt, atol=1e-12): + raise ValueError("delay grid is not uniform") + total = _decode_complex( + cast(dict[str, Any], _required(payload, "correlation")), + label="correlation", + ) + if total.shape != delay.shape: + raise ValueError("correlation data do not match the delay shape") + one_point = np.asarray(_required(payload, "one_point"), dtype=float) + if one_point.shape != (controls.phase_samples,) or not np.all( + np.isfinite(one_point) + ): + raise ValueError("one_point data have the wrong shape") + coherent, peaks = coherent_decomposition( + one_point, + model.drive_frequency, + delay, + ) + correlation = CorrelationResult( + delay, + total, + total - coherent, + coherent, + peaks, + METHOD, + { + "period_steps": float(period_steps), + "phase_samples": float(controls.phase_samples), + "dt": dt, + }, + ) + + diagnostics_payload = cast( + dict[str, Any], + _required(payload, "diagnostics"), + ) + diagnostic_keys = ( + "trace_error", + "hermiticity_error", + "minimum_density_eigenvalue", + "fixed_point_residual", + "floquet_transfer_residual", + ) + try: + diagnostics = { + key: float(diagnostics_payload[key]) for key in diagnostic_keys + } + except (KeyError, TypeError, ValueError) as exc: + raise ValueError("UniformTEMPO diagnostics are incomplete") from exc + if not all(np.isfinite(value) for value in diagnostics.values()): + raise ValueError("UniformTEMPO diagnostics contain non-finite values") + + transfer_dimension = int(_required(payload, "transfer_dimension")) + if transfer_dimension < 2: + raise ValueError("UniformTEMPO transfer dimension is invalid") + transfer_eigenvalues = _decode_complex( + cast(dict[str, Any], _required(payload, "transfer_eigenvalues")), + label="transfer_eigenvalues", + allow_empty=True, + ) + if transfer_eigenvalues.ndim != 1: + raise ValueError("transfer_eigenvalues must be one-dimensional") + transfer_eigenpair_residuals = np.asarray( + _required(payload, "transfer_eigenpair_residuals"), + dtype=float, + ) + if transfer_eigenpair_residuals.ndim != 1: + raise ValueError( + "transfer_eigenpair_residuals must be one-dimensional" + ) + if transfer_eigenvalues.shape != transfer_eigenpair_residuals.shape: + raise ValueError( + "transfer eigenvalues and residuals must have the same length" + ) + expected_poles = min(controls.pole_count, transfer_dimension - 1) + if len(transfer_eigenvalues) != expected_poles: + raise ValueError( + "UniformTEMPO returned " + f"{len(transfer_eigenvalues)} poles; expected {expected_poles}" + ) + if not np.all(np.isfinite(transfer_eigenpair_residuals)): + raise ValueError("transfer eigenpair residuals contain non-finite values") + if np.any(transfer_eigenpair_residuals < 0): + raise ValueError("transfer eigenpair residuals must be nonnegative") + + metadata: dict[str, float | int | str] = { + "dt": dt, + "period_steps": period_steps, + "phase_samples": controls.phase_samples, + "bond_dimension": int(_required(payload, "bond_dimension")), + "tolerance": controls.tolerance, + "julia_version": str(payload["julia_version"]), + "uniform_tempo_revision": str(payload["uniform_tempo_revision"]), + "manifest_sha256": manifest_hash, + "process_tensor_cache_hit": int( + bool(_required(payload, "process_tensor_cache_hit")) + ), + "transfer_dimension": transfer_dimension, + "pole_count": len(transfer_eigenvalues), + } + return UniformTempoResult( + method, + floquet_state_raw, + phase_states, + correlation, + diagnostics, + metadata, + transfer_eigenvalues, + transfer_eigenpair_residuals, + ) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/bath.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/bath.py new file mode 100644 index 000000000..b6ec2d00c --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/bath.py @@ -0,0 +1,45 @@ +"""Ohmic bosonic-bath conventions used throughout the project.""" + +from __future__ import annotations + +import numpy as np +from scipy.integrate import quad + +from .config import BathConfig + + +def ohmic_spectral_density(frequency: float | np.ndarray, bath: BathConfig) -> float | np.ndarray: + frequency_array = np.asarray(frequency) + result = bath.alpha * frequency_array * np.exp(-frequency_array / bath.cutoff) + result = np.where(frequency_array >= 0, result, 0.0) + return float(result) if result.ndim == 0 else result + + +def bose_occupation(frequency: float, temperature: float) -> float: + if frequency <= 0: + raise ValueError("frequency must be positive") + if temperature == 0: + return 0.0 + argument = frequency / temperature + if argument > 700: + return 0.0 + return float(1.0 / float(np.expm1(argument))) + + +def bath_correlation(time: float, bath: BathConfig) -> complex: + """Return for J(w)=alpha*w*exp(-w/wc), hbar=kB=1.""" + zero_temperature = bath.alpha * bath.cutoff**2 / (1 + 1j * bath.cutoff * time) ** 2 + if bath.temperature == 0 or bath.alpha == 0: + return complex(zero_temperature) + + def thermal_real(frequency: float) -> float: + density = float(ohmic_spectral_density(frequency, bath)) + return float( + 2 + * density + * bose_occupation(frequency, bath.temperature) + * np.cos(frequency * time) + ) + + thermal, _ = quad(thermal_real, 0, np.inf, epsabs=1e-10, epsrel=1e-9, limit=300) + return complex(zero_temperature + thermal) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/cli.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/cli.py new file mode 100644 index 000000000..290f6ec6a --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/cli.py @@ -0,0 +1,430 @@ +"""Command line interface for baseline generation and audit.""" + +from __future__ import annotations + +import argparse +import json +import sys +from dataclasses import asdict +from pathlib import Path +from typing import Literal + +import numpy as np + +from .experiments import ( + backend_comparison, + n2_bright_sweep, + n2_markov_heat_spectrum, + n3_sector_sweep, +) +from .io import read_result, write_result +from .plotting import ( + plot_backend_comparison, + plot_dark_diagnostics, + plot_error_maps, + plot_heat_spectrum, + plot_heat_valve_hero, + plot_model_variants, + plot_n2, + plot_n3, + plot_n3_error_maps, + plot_n3_pt_dynamics, + plot_n3_sector_heat, + plot_n4_pilot_comparison, + plot_odd_sector_difference, +) + +ExactBackend = Literal["uniform_tempo", "oqupy"] + + +def generate_baselines(output: Path, figures: Path, quick: bool = False) -> int: + n2 = n2_bright_sweep(np.linspace(0, 2, 17 if quick else 81)) + n3_values = np.geomspace(0.125, 16, 15 if quick else 49) + n3 = n3_sector_sweep(n3_values) + comparison = backend_comparison( + [0.005, 0.01] if quick else [0.005, 0.01, 0.025, 0.05], + steps_per_period=24 if quick else 48, + periods=4 if quick else 100, + ) + heat = n2_markov_heat_spectrum( + steps_per_period=32 if quick else 96, + correlation_periods=3 if quick else 16, + ) + paths = { + "n2_exact.json": n2, + "n3_exact.json": n3, + "backend_comparison.json": comparison, + "n2_markov_heat.json": heat, + } + for name, payload in paths.items(): + write_result(output / name, payload) + plot_n2(read_result(output / "n2_exact.json"), figures / "n2_exact") + plot_n3(read_result(output / "n3_exact.json"), figures / "n3_exact") + plot_backend_comparison( + read_result(output / "backend_comparison.json"), figures / "backend_comparison" + ) + plot_heat_spectrum( + read_result(output / "n2_markov_heat.json"), figures / "n2_markov_heat" + ) + return 0 + + +def audit_results(directory: Path, allow_unconverged: bool = False) -> int: + failures: list[str] = [] + files = sorted( + path + for path in directory.glob("*.json") + if path.name != "ARTIFACT_PROVENANCE.json" + ) + if not files: + failures.append("no result files") + for path in files: + try: + result = read_result(path) + except (ValueError, OSError) as exc: + failures.append(str(exc)) + continue + if not result["converged"] and not allow_unconverged: + failures.append(f"{path.name}: unconverged") + if "method" not in result: + failures.append(f"{path.name}: missing method label") + if failures: + for failure in failures: + print(f"FAIL: {failure}") + return 1 + print(f"PASS: audited {len(files)} result files") + return 0 + + +def generate_pt_baselines(output: Path, figures: Path) -> int: + from .pt_experiments import n2_pt_tempo_heat, n3_pt_tempo_dynamics + + n2 = n2_pt_tempo_heat() + write_result(output / "n2_pt_tempo_heat.json", n2) + plot_heat_spectrum( + read_result(output / "n2_pt_tempo_heat.json"), figures / "n2_pt_tempo_heat" + ) + n3 = n3_pt_tempo_dynamics() + write_result(output / "n3_pt_tempo_dynamics.json", n3) + plot_n3_pt_dynamics( + read_result(output / "n3_pt_tempo_dynamics.json"), + figures / "n3_pt_tempo_dynamics", + ) + return 0 + + +def generate_n3_paper_grid( + output: Path, + cache: Path, + figures: Path, + exact_backend: ExactBackend = "uniform_tempo", +) -> int: + from .paper_extension import run_n3_heat_grid + + manifest = run_n3_heat_grid(output, cache, exact_backend=exact_backend) + plot_n3_sector_heat(manifest, figures / "n3_sector_heat") + plot_odd_sector_difference(manifest, figures / "n3_odd_difference") + plot_dark_diagnostics(manifest, figures / "dark_diagnostics") + return 0 if manifest["converged"] else 1 + + +def generate_error_map( + output: Path, + cache: Path, + figures: Path, + exact_backend: ExactBackend = "uniform_tempo", +) -> int: + from .paper_extension import run_error_grid + + manifest = run_error_grid(output, cache, exact_backend=exact_backend) + plot_error_maps(manifest, figures / "error_maps") + return 0 + + +def generate_n3_error_map( + exact_manifest: Path, + output: Path, + figures: Path, +) -> int: + from .paper_extension import run_n3_error_map + + manifest = run_n3_error_map(exact_manifest, output) + plot_n3_error_maps(manifest, figures / "n3_error_maps") + return 0 if manifest["converged"] else 1 + + +def generate_n4_pilot( + output: Path, + cache: Path, + figures: Path, + sector: Literal["even", "odd"], + j: float, +) -> int: + from .paper_extension import run_n4_pilot + + result = run_n4_pilot(output, cache, sector=sector, j=j) + if result["converged"]: + j_label = f"{j:.2f}".replace(".", "p") + plot_n4_pilot_comparison( + result, + figures / f"n4_{sector}_j{j_label}_comparison", + ) + return 0 if result["converged"] else 1 + + +def generate_model_comparison( + output: Path, + cache: Path, + figures: Path, + exact_backend: ExactBackend = "uniform_tempo", + full_kac: bool = False, +) -> int: + from .paper_extension import run_model_comparison + + manifest = run_model_comparison( + output, + cache, + exact_backend=exact_backend, + full_kac=full_kac, + ) + plot_model_variants(manifest, figures / "model_variants") + return 0 if manifest.get("locally_complete", manifest["converged"]) else 1 + + +def paper_audit(directory: Path) -> int: + from .paper_extension import audit_paper_results + + passed, failures = audit_paper_results(directory) + if not passed: + for failure in failures: + print(f"FAIL: {failure}") + return 1 + print("PASS: paper extension manifests and convergence evidence are complete") + return 0 + + +def generate_heat_valve( + output: Path, + cache: Path, + figures: Path, + *, + full: bool, +) -> int: + from .convergence import ConvergenceCache, atomic_write_result + from .heat_valve import ( + HeatValvePoint, + ValveNumerics, + build_heat_valve_manifest, + isolated_valve_scan, + run_uniform_valve_point, + ) + from .heat_valve_audit import audit_heat_valve_manifest + + floquet_steps = 360 if full else 240 + xi_values = np.linspace(1.8, 4.0, 45) + isolated = isolated_valve_scan( + HeatValvePoint( + n=n, + xi=float(xi), + drive_frequency=3.0, + floquet_steps=floquet_steps, + ) + for n in (1, 2, 3) + for xi in xi_values + ) + atomic_write_result(output / "isolated_scan.json", isolated) + selection = build_heat_valve_manifest(isolated) + selected = [ + HeatValvePoint( + n=int(item["n"]), # type: ignore[arg-type] + xi=float(item["xi"]), + j=float(item["j"]), + omega=float(item["omega"]), + drive_frequency=float(item["drive_frequency"]), + alpha=float(item["alpha"]), + cutoff=float(item["cutoff"]), + temperature=float(item["temperature"]), + floquet_steps=int(item["floquet_steps"]), + ) + for item in selection["selected_points"] + if full or int(item["n"]) == 3 + ] + numerical_controls = ValveNumerics() + result_cache = ConvergenceCache(cache) + results = [] + for point in selected: + result = run_uniform_valve_point( + point, + numerical_controls, + result_cache, + ) + results.append(result) + atomic_write_result( + output / f"n{point.n}_xi{point.xi:.2f}.json", + result, + ) + manifest = build_heat_valve_manifest(isolated, results) + audit = audit_heat_valve_manifest(manifest) + manifest["audit"] = asdict(audit) + atomic_write_result(output / "heat_valve_manifest.json", manifest) + plot_heat_valve_hero(manifest, figures / "heat_valve_hero") + requested = 9 if full else 3 + return 0 if len(results) == requested and all( + bool(item.get("converged", False)) for item in results + ) else 1 + + +def heat_valve_audit(directory: Path) -> int: + from .heat_valve_audit import audit_heat_valve_manifest + + path = directory / "heat_valve_manifest.json" + if not path.is_file(): + print(f"FAIL: missing {path}") + return 1 + try: + manifest = json.loads(path.read_text(encoding="utf-8")) + audit = audit_heat_valve_manifest(manifest) + except (OSError, json.JSONDecodeError, ValueError, KeyError, TypeError) as exc: + print(f"FAIL: malformed heat-valve manifest: {exc}") + return 1 + if not audit.dark_channel_passed: + for failure in audit.failures: + print(f"FAIL: {failure}") + return 1 + print("PASS: pole-resolved Floquet dark-channel gates are complete") + return 0 + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(prog="floquet-if") + subparsers = parser.add_subparsers(dest="command", required=True) + baseline = subparsers.add_parser("baselines") + baseline.add_argument("--output", type=Path, default=Path("results")) + baseline.add_argument("--figures", type=Path, default=Path("figures")) + baseline.add_argument("--quick", action="store_true") + pt_baseline = subparsers.add_parser("pt-baselines") + pt_baseline.add_argument("--output", type=Path, default=Path("results")) + pt_baseline.add_argument("--figures", type=Path, default=Path("figures")) + audit = subparsers.add_parser("audit") + audit.add_argument("directory", type=Path) + audit.add_argument("--allow-unconverged", action="store_true") + for command in ("n3-heat-grid", "error-map", "model-comparison"): + paper = subparsers.add_parser(command) + paper.add_argument("--output", type=Path, default=Path("results/paper")) + paper.add_argument("--cache", type=Path, default=Path("results/cache")) + paper.add_argument("--figures", type=Path, default=Path("figures/paper")) + paper.add_argument( + "--exact-backend", + choices=("uniform_tempo", "oqupy"), + default="uniform_tempo", + ) + if command == "model-comparison": + paper.add_argument( + "--full-kac", + action="store_true", + help="run cluster-scale timestep and phase refinement for Kac variants", + ) + n3_error = subparsers.add_parser("n3-error-map") + n3_error.add_argument( + "--exact-manifest", + type=Path, + default=Path("results/paper/n3_heat_manifest.json"), + ) + n3_error.add_argument("--output", type=Path, default=Path("results/paper")) + n3_error.add_argument("--figures", type=Path, default=Path("figures/paper")) + n4_pilot = subparsers.add_parser("n4-pilot") + n4_pilot.add_argument("--output", type=Path, default=Path("results/paper")) + n4_pilot.add_argument( + "--cache", + type=Path, + default=Path("results/cache/n4_uniform_tempo"), + ) + n4_pilot.add_argument("--figures", type=Path, default=Path("figures/paper")) + n4_pilot.add_argument("--sector", choices=("even", "odd"), default="odd") + n4_pilot.add_argument("--j", type=float, default=0.25) + paper_check = subparsers.add_parser("paper-audit") + paper_check.add_argument("directory", type=Path, default=Path("results/paper")) + heat_valve = subparsers.add_parser("heat-valve") + heat_valve.add_argument( + "--output", + type=Path, + default=Path("results/heat-valve"), + ) + heat_valve.add_argument( + "--cache", + type=Path, + default=Path("results/cache/uniform_tempo"), + ) + heat_valve.add_argument( + "--figures", + type=Path, + default=Path("figures/heat-valve"), + ) + mode = heat_valve.add_mutually_exclusive_group() + mode.add_argument("--pilot", action="store_true") + mode.add_argument("--full", action="store_true") + heat_check = subparsers.add_parser("heat-valve-audit") + heat_check.add_argument("directory", type=Path) + return parser + + +def main(argv: list[str] | None = None) -> int: + arguments = build_parser().parse_args(argv) + if arguments.command == "baselines": + return generate_baselines(arguments.output, arguments.figures, arguments.quick) + if arguments.command == "audit": + return audit_results(arguments.directory, arguments.allow_unconverged) + if arguments.command == "pt-baselines": + return generate_pt_baselines(arguments.output, arguments.figures) + if arguments.command == "n3-heat-grid": + return generate_n3_paper_grid( + arguments.output, + arguments.cache, + arguments.figures, + arguments.exact_backend, + ) + if arguments.command == "error-map": + return generate_error_map( + arguments.output, + arguments.cache, + arguments.figures, + arguments.exact_backend, + ) + if arguments.command == "n3-error-map": + return generate_n3_error_map( + arguments.exact_manifest, + arguments.output, + arguments.figures, + ) + if arguments.command == "n4-pilot": + return generate_n4_pilot( + arguments.output, + arguments.cache, + arguments.figures, + arguments.sector, + arguments.j, + ) + if arguments.command == "model-comparison": + return generate_model_comparison( + arguments.output, + arguments.cache, + arguments.figures, + arguments.exact_backend, + arguments.full_kac, + ) + if arguments.command == "paper-audit": + return paper_audit(arguments.directory) + if arguments.command == "heat-valve": + return generate_heat_valve( + arguments.output, + arguments.cache, + arguments.figures, + full=arguments.full, + ) + if arguments.command == "heat-valve-audit": + return heat_valve_audit(arguments.directory) + return 2 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/config.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/config.py new file mode 100644 index 000000000..7f0fd0690 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/config.py @@ -0,0 +1,131 @@ +"""Validated scientific and numerical configuration.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Literal + +import yaml + +Normalization = Literal["bounded", "kac", "collective"] +DriveNormalization = Literal["coupling", "per_spin"] + + +@dataclass(frozen=True) +class ModelConfig: + n: int = 2 + j: float = 0.5 + omega: float = 1.0 + drive_amplitude: float = 0.3 + drive_frequency: float = 1.0 + normalization: Normalization = "bounded" + drive_normalization: DriveNormalization = "coupling" + counterterm: bool = False + counterterm_strength: float = 0.0 + + def __post_init__(self) -> None: + if self.n not in (1, 2, 3, 4): + raise ValueError("n must be one of 1, 2, 3, 4") + if self.j < 0: + raise ValueError("j must be nonnegative") + if self.omega <= 0: + raise ValueError("omega must be positive") + if self.drive_amplitude < 0: + raise ValueError("drive_amplitude must be nonnegative") + if self.drive_frequency <= 0: + raise ValueError("drive_frequency must be positive") + if isinstance(self.counterterm_strength, bool) or self.counterterm_strength < 0: + raise ValueError("counterterm_strength must be a nonnegative coefficient") + if self.counterterm and self.counterterm_strength <= 0: + raise ValueError( + "counterterm=True requires a positive counterterm_strength" + ) + if self.normalization not in ("bounded", "kac", "collective"): + raise ValueError("unknown normalization") + if self.drive_normalization not in ("coupling", "per_spin"): + raise ValueError("unknown drive normalization") + + @property + def eta(self) -> float: + if self.normalization == "bounded": + return 1.0 / self.n + if self.normalization == "kac": + return float(1.0 / self.n**0.5) + return 1.0 + + @property + def drive_eta(self) -> float: + if self.drive_normalization == "coupling": + return self.eta + return 1.0 + + @property + def period(self) -> float: + import math + + return 2 * math.pi / self.drive_frequency + + +@dataclass(frozen=True) +class BathConfig: + alpha: float = 0.05 + cutoff: float = 2.5 + temperature: float = 0.0 + + def __post_init__(self) -> None: + if self.alpha < 0: + raise ValueError("alpha must be nonnegative") + if self.cutoff <= 0: + raise ValueError("cutoff must be positive") + if self.temperature < 0: + raise ValueError("temperature must be nonnegative") + + +@dataclass(frozen=True) +class NumericsConfig: + drive_steps: int = 240 + memory_steps: int = 4 + periods: int = 80 + correlation_periods: int = 20 + harmonic_cutoff: int = 6 + convergence_tolerance: float = 1e-3 + + def __post_init__(self) -> None: + if self.drive_steps < 8: + raise ValueError("drive_steps must be at least 8") + if self.memory_steps < 1: + raise ValueError("memory_steps must be positive") + if self.periods < 1 or self.correlation_periods < 1: + raise ValueError("period counts must be positive") + if self.harmonic_cutoff < 0: + raise ValueError("harmonic_cutoff must be nonnegative") + if self.convergence_tolerance <= 0: + raise ValueError("convergence_tolerance must be positive") + + +@dataclass(frozen=True) +class RunConfig: + model: ModelConfig = field(default_factory=ModelConfig) + bath: BathConfig = field(default_factory=BathConfig) + numerics: NumericsConfig = field(default_factory=NumericsConfig) + sector: Literal["full", "triplet", "singlet", "even", "odd"] = "full" + method: Literal["closed", "floquet_markov", "finite_memory_if"] = "closed" + + +def _section(cls: type[Any], data: dict[str, Any], key: str) -> Any: + return cls(**data.get(key, {})) + + +def load_config(path: Path) -> RunConfig: + """Load a validated run configuration from YAML.""" + data = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + if not isinstance(data, dict): + raise ValueError("configuration root must be a mapping") + return RunConfig( + model=_section(ModelConfig, data, "model"), + bath=_section(BathConfig, data, "bath"), + numerics=_section(NumericsConfig, data, "numerics"), + sector=data.get("sector", "full"), + method=data.get("method", "closed"), + ) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/convergence.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/convergence.py new file mode 100644 index 000000000..31afeef96 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/convergence.py @@ -0,0 +1,125 @@ +"""Content-addressed caching and numerical convergence metrics.""" + +from __future__ import annotations + +import hashlib +import json +import os +import tempfile +from collections.abc import Mapping +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Any, cast + +import numpy as np +from numpy.typing import NDArray +from scipy.integrate import trapezoid + + +@dataclass(frozen=True) +class ConvergencePoint: + """Complete numerical controls for one backend evaluation.""" + + model: Mapping[str, Any] + bath: Mapping[str, Any] + sector: str + steps_per_period: int + periods: int + delay_periods: int + memory_steps: int + epsrel: float + + +@dataclass(frozen=True) +class ConvergenceThresholds: + state: float = 5e-2 + correlation: float = 5e-2 + heat: float = 5e-2 + phase: float = 1e-3 + trace: float = 5e-3 + + +def fingerprint(point: Mapping[str, Any] | ConvergencePoint, commit: str) -> str: + """Return a stable SHA-256 key for a point and source revision.""" + payload: Any = asdict(point) if isinstance(point, ConvergencePoint) else point + encoded = json.dumps( + {"commit": commit, "point": payload}, + sort_keys=True, + separators=(",", ":"), + default=str, + ).encode() + return hashlib.sha256(encoded).hexdigest() + + +def atomic_write_result(path: Path, payload: Mapping[str, Any]) -> None: + """Atomically replace a JSON cache entry in its destination directory.""" + path.parent.mkdir(parents=True, exist_ok=True) + descriptor, temporary_name = tempfile.mkstemp( + dir=path.parent, prefix=f".{path.name}.", suffix=".tmp" + ) + temporary = Path(temporary_name) + try: + with os.fdopen(descriptor, "w", encoding="utf-8") as handle: + json.dump(payload, handle, indent=2, sort_keys=True, default=str) + handle.flush() + os.fsync(handle.fileno()) + os.replace(temporary, path) + finally: + temporary.unlink(missing_ok=True) + + +def state_residual( + candidate: NDArray[np.complex128], reference: NDArray[np.complex128] +) -> float: + if candidate.shape != reference.shape: + raise ValueError("state shapes do not match") + return float(np.linalg.norm(candidate - reference)) + + +def curve_residual( + candidate_grid: NDArray[np.float64], + candidate: NDArray[np.complex128] | NDArray[np.float64], + reference_grid: NDArray[np.float64], + reference: NDArray[np.complex128] | NDArray[np.float64], +) -> float: + """Normalized L1 residual on an identical one-dimensional grid.""" + if ( + candidate_grid.shape != reference_grid.shape + or candidate.shape != reference.shape + or candidate.shape != candidate_grid.shape + or not np.allclose(candidate_grid, reference_grid, atol=1e-12, rtol=1e-12) + ): + raise ValueError("curve grids or values do not match") + numerator = float(trapezoid(abs(candidate - reference), reference_grid)) + denominator = float(trapezoid(abs(reference), reference_grid)) + 1e-15 + return numerator / denominator + + +class ConvergenceCache: + """Small JSON cache whose entries are complete only after atomic rename.""" + + def __init__(self, directory: Path): + self.directory = directory + + def path_for(self, key: str) -> Path: + if len(key) != 64 or any(character not in "0123456789abcdef" for character in key): + raise ValueError("cache key must be a lowercase SHA-256 digest") + return self.directory / f"{key}.json" + + def contains(self, key: str) -> bool: + return self.path_for(key).is_file() + + def load(self, key: str) -> dict[str, Any]: + path = self.path_for(key) + value = json.loads(path.read_text(encoding="utf-8")) + if value.get("fingerprint") != key or value.get("complete") is not True: + raise ValueError(f"invalid or incomplete cache entry {path}") + return cast(dict[str, Any], value) + + def store(self, key: str, payload: Mapping[str, Any]) -> Path: + path = self.path_for(key) + atomic_write_result( + path, + {"fingerprint": key, "complete": True, **dict(payload)}, + ) + return path diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/correlations.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/correlations.py new file mode 100644 index 000000000..9730186c5 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/correlations.py @@ -0,0 +1,143 @@ +"""Period-averaged correlations and coherent-harmonic decomposition.""" + +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np +from numpy.typing import NDArray + +from .operators import ComplexMatrix + + +@dataclass(frozen=True) +class DeltaCorrelationPeak: + harmonic: int + frequency: float + correlation_weight: float + + +@dataclass(frozen=True) +class CorrelationResult: + delays: NDArray[np.float64] + total: NDArray[np.complex128] + connected: NDArray[np.complex128] + coherent: NDArray[np.float64] + delta_peaks: tuple[DeltaCorrelationPeak, ...] + method: str + metadata: dict[str, float | str] + + +def coherent_decomposition( + one_point: NDArray[np.float64], + drive_frequency: float, + delays: NDArray[np.float64], + threshold: float = 1e-12, +) -> tuple[NDArray[np.float64], tuple[DeltaCorrelationPeak, ...]]: + """Compute period-averaged factorized correlation from one micromotion period.""" + if one_point.ndim != 1 or len(one_point) < 2: + raise ValueError("one_point must contain one sampled drive period") + coefficients = np.fft.fft(one_point) / len(one_point) + coherent = np.full_like(delays, abs(coefficients[0]) ** 2, dtype=np.float64) + peaks: list[DeltaCorrelationPeak] = [] + if abs(coefficients[0]) ** 2 >= threshold: + peaks.append(DeltaCorrelationPeak(0, 0.0, float(abs(coefficients[0]) ** 2))) + maximum = len(one_point) // 2 + for harmonic in range(1, maximum + 1): + weight = float(2 * abs(coefficients[harmonic]) ** 2) + if weight >= threshold: + frequency = harmonic * drive_frequency + coherent += weight * np.cos(frequency * delays) + peaks.append(DeltaCorrelationPeak(harmonic, frequency, weight)) + return coherent, tuple(peaks) + + +def unitary_period_correlation( + step_propagators: tuple[ComplexMatrix, ...], + phase_densities: NDArray[np.complex128], + operator: ComplexMatrix, + dt: float, + delay_steps: int, + drive_frequency: float, + method: str = "closed_unitary", +) -> CorrelationResult: + """Compute Cbar(tau) by exact finite-system operator insertions. + + This routine is valid for closed dynamics. Using open-system reduced + propagators would constitute a quantum-regression approximation and must + carry a different method label. + """ + period_steps = len(step_propagators) + if phase_densities.shape[0] != period_steps: + raise ValueError("one phase density is required per step in a period") + total = np.zeros(delay_steps + 1, dtype=np.complex128) + one_point = np.real( + np.einsum("ij,tji->t", operator, phase_densities, optimize=True) + ) + for phase in range(period_steps): + inserted = operator @ phase_densities[phase] + propagation = np.eye(operator.shape[0], dtype=np.complex128) + total[0] += np.trace(operator @ inserted) + for delay in range(1, delay_steps + 1): + piece = step_propagators[(phase + delay - 1) % period_steps] + propagation = piece @ propagation + evolved = propagation @ inserted @ propagation.conj().T + total[delay] += np.trace(operator @ evolved) + total /= period_steps + delays = np.arange(delay_steps + 1, dtype=np.float64) * dt + coherent, peaks = coherent_decomposition(one_point, drive_frequency, delays) + return CorrelationResult( + delays, + total, + total - coherent, + coherent, + peaks, + method, + {"period_steps": float(period_steps), "dt": dt}, + ) + + +def superoperator_period_correlation( + step_maps: NDArray[np.complex128], + phase_densities: NDArray[np.complex128], + operator: ComplexMatrix, + dt: float, + delay_steps: int, + drive_frequency: float, + method: str = "floquet_markov_qr", +) -> CorrelationResult: + """Two-time correlation using a periodic Markovian dynamical map. + + This is the quantum regression theorem and is only used for the explicitly + labeled Markovian backend. + """ + period_steps = len(step_maps) + if phase_densities.shape[0] != period_steps: + raise ValueError("one phase density is required per step in a period") + dimension = operator.shape[0] + total = np.zeros(delay_steps + 1, dtype=np.complex128) + one_point = np.real( + np.einsum("ij,tji->t", operator, phase_densities, optimize=True) + ) + measurement = operator.T.reshape(dimension**2, order="F") + for phase in range(period_steps): + inserted = (operator @ phase_densities[phase]).reshape( + dimension**2, order="F" + ) + vector = inserted + total[0] += measurement @ vector + for delay in range(1, delay_steps + 1): + vector = step_maps[(phase + delay - 1) % period_steps] @ vector + total[delay] += measurement @ vector + total /= period_steps + delays = np.arange(delay_steps + 1, dtype=np.float64) * dt + coherent, peaks = coherent_decomposition(one_point, drive_frequency, delays) + return CorrelationResult( + delays, + total, + total - coherent, + coherent, + peaks, + method, + {"period_steps": float(period_steps), "dt": dt}, + ) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/dark_channels.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/dark_channels.py new file mode 100644 index 000000000..af99a667c --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/dark_channels.py @@ -0,0 +1,128 @@ +"""Harmonic-resolved Floquet transition and dark-channel diagnostics.""" + +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np +from numpy.typing import NDArray + +from .floquet import FloquetSolution, micromotion +from .operators import ComplexMatrix + + +@dataclass(frozen=True) +class FloquetTransition: + source: int + target: int + harmonic: int + emitted_frequency: float + amplitude: complex + weight: float + + +@dataclass(frozen=True) +class DarkCandidate: + transition: FloquetTransition + relative_weight: float + + +def periodic_modes(solution: FloquetSolution) -> NDArray[np.complex128]: + cumulative = micromotion(solution)[:-1] + dt = solution.period / len(cumulative) + values = [] + for index, propagator in enumerate(cumulative): + time = index * dt + values.append( + propagator + @ solution.modes + @ np.diag(np.exp(1j * solution.quasienergies * time)) + ) + return np.asarray(values) + + +def floquet_matrix_elements( + solution: FloquetSolution, + operator: ComplexMatrix, + harmonic_cutoff: int, + threshold: float = 0.0, +) -> tuple[FloquetTransition, ...]: + """Return S_ab^(m) under the project's Fourier convention.""" + if harmonic_cutoff < 0: + raise ValueError("harmonic_cutoff must be nonnegative") + modes = periodic_modes(solution) + samples = len(modes) + omega_d = 2 * np.pi / solution.period + instantaneous = np.einsum( + "tai,ab,tbj->tij", modes.conj(), operator, modes, optimize=True + ) + records: list[FloquetTransition] = [] + for harmonic in range(-harmonic_cutoff, harmonic_cutoff + 1): + phase = np.exp(-1j * harmonic * omega_d * np.arange(samples) * solution.period / samples) + fourier = np.einsum("t,tij->ij", phase, instantaneous) / samples + for target in range(fourier.shape[0]): + for source in range(fourier.shape[1]): + amplitude = complex(fourier[target, source]) + weight = float(abs(amplitude) ** 2) + if weight >= threshold: + records.append( + FloquetTransition( + source=source, + target=target, + harmonic=harmonic, + emitted_frequency=float( + solution.quasienergies[source] + - solution.quasienergies[target] + + harmonic * omega_d + ), + amplitude=amplitude, + weight=weight, + ) + ) + return tuple(records) + + +def harmonic_sum_rule( + solution: FloquetSolution, + operator: ComplexMatrix, + harmonic_cutoff: int, +) -> float: + """Return maximum relative Parseval residual over mode pairs.""" + modes = periodic_modes(solution) + instantaneous = np.einsum( + "tai,ab,tbj->tij", modes.conj(), operator, modes, optimize=True + ) + direct = np.mean(abs(instantaneous) ** 2, axis=0) + records = floquet_matrix_elements(solution, operator, harmonic_cutoff) + summed = np.zeros_like(direct, dtype=float) + for record in records: + summed[record.target, record.source] += record.weight + return float(np.max(abs(summed - direct) / (direct + 1e-15))) + + +def period_variance( + phase_densities: NDArray[np.complex128], operator: ComplexMatrix +) -> float: + first = np.einsum("ij,tji->t", operator, phase_densities, optimize=True) + second = np.einsum( + "ij,tji->t", operator @ operator, phase_densities, optimize=True + ) + return float(np.real(np.mean(second - first * first))) + + +def dark_candidates( + transitions: tuple[FloquetTransition, ...], + relative_threshold: float = 1e-4, +) -> tuple[DarkCandidate, ...]: + if not 0 <= relative_threshold <= 1: + raise ValueError("relative_threshold must lie in [0, 1]") + if not transitions: + return () + maximum = max(record.weight for record in transitions) + if maximum == 0: + return tuple(DarkCandidate(record, 0.0) for record in transitions) + return tuple( + DarkCandidate(record, record.weight / maximum) + for record in transitions + if record.weight / maximum <= relative_threshold + ) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/error_map.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/error_map.py new file mode 100644 index 000000000..65edebe7d --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/error_map.py @@ -0,0 +1,152 @@ +"""Exact PT-TEMPO versus Floquet-Markov comparison metrics and audits.""" + +from __future__ import annotations + +from dataclasses import asdict, dataclass +from typing import Any, cast + +import numpy as np +from numpy.typing import NDArray + +from .convergence import curve_residual + + +@dataclass(frozen=True) +class ErrorMetrics: + trace_distance: float + correlation: float + heat: float + + +def trace_distance( + first: NDArray[np.complex128], second: NDArray[np.complex128] +) -> float: + if first.shape != second.shape or first.ndim != 2 or first.shape[0] != first.shape[1]: + raise ValueError("density matrices must have the same square shape") + difference = (first - second + (first - second).conj().T) / 2 + return float(0.5 * np.sum(abs(np.linalg.eigvalsh(difference)))) + + +def correlation_error( + exact_grid: NDArray[np.float64], + exact: NDArray[np.complex128], + markov_grid: NDArray[np.float64], + markov: NDArray[np.complex128], +) -> float: + return curve_residual(markov_grid, markov, exact_grid, exact) + + +def heat_error( + exact_grid: NDArray[np.float64], + exact: NDArray[np.float64], + markov_grid: NDArray[np.float64], + markov: NDArray[np.float64], +) -> float: + return curve_residual(markov_grid, markov, exact_grid, exact) + + +def _complex_array(value: dict[str, Any]) -> NDArray[np.complex128]: + return cast( + NDArray[np.complex128], + np.asarray(value["real"], dtype=float) + + 1j * np.asarray(value["imag"], dtype=float), + ) + + +def build_error_record( + exact: dict[str, Any], markov: dict[str, Any] +) -> dict[str, Any]: + """Build a comparison only after checking scientific compatibility.""" + if not bool(exact.get("converged")): + raise ValueError("exact PT-TEMPO input must be converged") + exact_method = str(exact.get("method", "")) + if exact_method not in { + "pt_tempo_multitime", + "uniform_tempo_floquet_multitime", + }: + raise ValueError("exact input must use an approved process-tensor method") + if exact.get("model_hash") != markov.get("model_hash"): + raise ValueError("model_hash mismatch") + exact_normalization = exact.get("model", {}).get("normalization") + markov_normalization = markov.get("model", {}).get("normalization") + if exact_normalization != markov_normalization: + raise ValueError("normalization mismatch") + exact_frequency = np.asarray(exact["frequency"], dtype=float) + markov_frequency = np.asarray(markov["frequency"], dtype=float) + if ( + exact_frequency.shape != markov_frequency.shape + or not np.allclose(exact_frequency, markov_frequency) + ): + raise ValueError("frequency grid mismatch") + exact_delay = np.asarray(exact["correlation"]["delay"], dtype=float) + markov_delay = np.asarray(markov["correlation"]["delay"], dtype=float) + if exact_delay.shape != markov_delay.shape or not np.allclose( + exact_delay, markov_delay + ): + raise ValueError("correlation grid mismatch") + metrics = ErrorMetrics( + trace_distance( + _complex_array(exact["phase_state"]), + _complex_array(markov["phase_state"]), + ), + correlation_error( + exact_delay, + _complex_array(exact["correlation"]["connected"]), + markov_delay, + _complex_array(markov["correlation"]["connected"]), + ), + heat_error( + exact_frequency, + np.asarray(exact["continuous"], dtype=float), + markov_frequency, + np.asarray(markov["continuous"], dtype=float), + ), + ) + return { + "status": "converged", + "model_hash": exact["model_hash"], + "exact_method": exact["method"], + "markov_method": markov["method"], + "metrics": asdict(metrics), + "convergence_evidence": exact.get("evidence", []), + } + + +def audit_grid_manifest( + manifest: dict[str, Any], + alphas: tuple[float, ...] = (0.025, 0.05, 0.1), + drive_ratios: tuple[float, ...] = (0.75, 1.0, 1.25), +) -> dict[str, Any]: + """Require each requested cell to be converged or explicitly masked.""" + expected = {(float(alpha), float(ratio)) for alpha in alphas for ratio in drive_ratios} + points = manifest.get("points") + if not isinstance(points, list): + raise ValueError("manifest points must be a list") + observed: dict[tuple[float, float], dict[str, Any]] = {} + for point in points: + key = (float(point["alpha"]), float(point["drive_ratio"])) + if key in observed: + raise ValueError(f"duplicate grid point {key}") + observed[key] = point + if set(observed) != expected: + missing = sorted(expected - set(observed)) + extra = sorted(set(observed) - expected) + raise ValueError(f"grid mismatch; missing={missing}, extra={extra}") + masked = 0 + for key, point in observed.items(): + status = point.get("status") + if status == "converged": + if not isinstance(point.get("metrics"), dict): + raise ValueError(f"converged point {key} lacks metrics") + elif status == "resource_ceiling": + if point.get("metrics") is not None: + raise ValueError(f"masked point {key} must not fabricate metrics") + masked += 1 + else: + raise ValueError(f"invalid status for point {key}: {status}") + return { + "complete": True, + "requested_points": len(expected), + "converged_points": len(expected) - masked, + "masked_points": masked, + } diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/experiments.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/experiments.py new file mode 100644 index 000000000..cec12d945 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/experiments.py @@ -0,0 +1,273 @@ +"""Reproducible baseline experiments used by the report.""" + +from __future__ import annotations + +from dataclasses import asdict + +import numpy as np + +from .backends.finite_memory import FiniteMemoryBackend +from .backends.floquet_markov import FloquetMarkovBackend +from .config import BathConfig, ModelConfig, Normalization +from .correlations import superoperator_period_correlation +from .heat_current import heat_current_spectrum +from .models import coupling_operator, ising_hamiltonian +from .spectra import diagonalize, transitions +from .symmetry import n2_sectors, n3_reflection_sectors, project + + +def n2_bright_sweep( + j_values: np.ndarray, + omega: float = 1.0, + normalization: Normalization = "bounded", +) -> dict[str, object]: + _, triplet = n2_sectors() + records = [] + maximum_residual = 0.0 + for j in j_values: + cfg = ModelConfig(n=2, j=float(j), omega=omega, normalization=normalization) + h = project(ising_hamiltonian(cfg), triplet) + s = project(coupling_operator(cfg), triplet) + spectrum = diagonalize(h) + items = transitions(spectrum, spectrum, s, threshold=1e-10) + low = next(item for item in items if item.source == 0 and item.frequency > 0) + high = next(item for item in items if item.source == 1 and item.target == 2) + energy = float(np.sqrt(j**2 + omega**2)) + analytic = { + "gap_low": energy - float(j), + "gap_high": energy + float(j), + "weight_low": 2 * cfg.eta**2 * (1 + float(j) / energy), + "weight_high": 2 * cfg.eta**2 * (1 - float(j) / energy), + } + numerical = { + "gap_low": low.frequency, + "gap_high": high.frequency, + "weight_low": low.weight, + "weight_high": high.weight, + } + maximum_residual = max( + maximum_residual, + max(abs(numerical[key] - analytic[key]) for key in analytic), + ) + records.append( + { + "j": float(j), + **numerical, + **{f"analytic_{key}": value for key, value in analytic.items()}, + } + ) + return { + "method": "exact_diagonalization", + "converged": maximum_residual < 1e-10, + "diagnostics": {"maximum_analytic_residual": maximum_residual}, + "data": records, + } + + +def n3_sector_sweep( + j_values: np.ndarray, + omega: float = 1.0, + normalization: Normalization = "bounded", +) -> dict[str, object]: + odd, even = n3_reflection_sectors() + records = [] + odd_gap_spread = 0.0 + odd_gaps = [] + for j in j_values: + cfg = ModelConfig(n=3, j=float(j), omega=omega, normalization=normalization) + full_h = ising_hamiltonian(cfg) + full_s = coupling_operator(cfg) + even_spectrum = diagonalize(project(full_h, even)) + odd_spectrum = diagonalize(project(full_h, odd)) + odd_gap = float(odd_spectrum.energies[1] - odd_spectrum.energies[0]) + odd_gaps.append(odd_gap) + even_operator = project(full_s, even) + bright = sorted( + [ + item + for item in transitions(even_spectrum, even_spectrum, even_operator, 1e-10) + if item.source == 0 and item.frequency > 0 + ], + key=lambda item: item.frequency, + ) + primary = bright[0] + records.append( + { + "j": float(j), + "odd_gap": odd_gap, + "primary_even_gap": primary.frequency, + "primary_even_weight": primary.weight, + "cat_ratio": ( + primary.frequency * 4 * float(j) ** 2 / omega**3 if j > 0 else None + ), + } + ) + odd_gap_spread = float(np.ptp(odd_gaps)) + return { + "method": "exact_diagonalization", + "converged": odd_gap_spread < 1e-10, + "diagnostics": {"odd_gap_spread": odd_gap_spread}, + "data": records, + } + + +def backend_comparison( + alphas: list[float], + j: float = 0.5, + omega: float = 1.0, + drive_amplitude: float = 0.2, + steps_per_period: int = 48, + periods: int = 100, +) -> dict[str, object]: + """Compare two explicitly approximate backends for the N=2 triplet.""" + _, triplet = n2_sectors() + gap = float(np.sqrt(j**2 + omega**2) - j) + cfg = ModelConfig( + n=2, + j=j, + omega=omega, + drive_amplitude=drive_amplitude, + drive_frequency=gap, + ) + h0 = project(ising_hamiltonian(cfg), triplet) + s = project(coupling_operator(cfg), triplet) + + def hamiltonian(time: float) -> np.ndarray: + return np.asarray( + h0 + cfg.drive_amplitude * np.cos(cfg.drive_frequency * time) * s, + dtype=np.complex128, + ) + + ground = diagonalize(h0).states[:, 0] + rho0 = np.outer(ground, ground.conj()) + dt = cfg.period / steps_per_period + records = [] + for alpha in alphas: + bath = BathConfig(alpha=alpha) + markov = FloquetMarkovBackend().run( + hamiltonian, s, bath, cfg.period, steps_per_period, 4 + ) + memory = FiniteMemoryBackend().run( + hamiltonian, + s, + rho0, + bath, + dt, + steps_per_period * periods, + 1, + ) + memory_phase_residual = float( + np.linalg.norm( + memory.density_matrices[-1] + - memory.density_matrices[-1 - steps_per_period] + ) + ) + trace_distance = float( + 0.5 + * np.sum( + np.abs( + np.linalg.eigvalsh( + memory.density_matrices[-1] - markov.density_matrices[0] + ) + ) + ) + ) + records.append( + { + "alpha": alpha, + "trace_distance": trace_distance, + "finite_memory_phase_residual": memory_phase_residual, + "finite_memory_trace_error": memory.diagnostics["trace_error"], + "finite_memory_min_eigenvalue": memory.diagnostics[ + "minimum_density_eigenvalue" + ], + "markov_rate_residual": markov.diagnostics["rate_residual"], + } + ) + converged = all( + record["finite_memory_phase_residual"] < 5e-3 + and record["finite_memory_min_eigenvalue"] > -1e-5 + for record in records + ) + return { + "method": "finite_memory_if_vs_floquet_markov", + "converged": converged, + "diagnostics": { + "memory_steps": 1, + "steps_per_period": steps_per_period, + "periods": periods, + "warning": ( + "This is an approximation-to-approximation comparison, " + "not an exact Floquet-IF error map." + ), + }, + "model": asdict(cfg), + "data": records, + } + + +def n2_markov_heat_spectrum( + j: float = 0.5, + omega: float = 1.0, + alpha: float = 0.05, + drive_amplitude: float = 0.2, + steps_per_period: int = 96, + correlation_periods: int = 16, +) -> dict[str, object]: + """Generate the explicitly approximate N=2 Floquet-Markov heat spectrum.""" + _, triplet = n2_sectors() + gap = float(np.sqrt(j**2 + omega**2) - j) + cfg = ModelConfig( + n=2, + j=j, + omega=omega, + drive_amplitude=drive_amplitude, + drive_frequency=gap, + ) + h0 = project(ising_hamiltonian(cfg), triplet) + coupling = project(coupling_operator(cfg), triplet) + + def hamiltonian(time: float) -> np.ndarray: + return np.asarray( + h0 + drive_amplitude * np.cos(cfg.drive_frequency * time) * coupling, + dtype=np.complex128, + ) + + bath = BathConfig(alpha=alpha) + markov = FloquetMarkovBackend().run( + hamiltonian, + coupling, + bath, + cfg.period, + steps_per_period, + 5, + ) + if markov.step_maps is None: + raise RuntimeError("Floquet-Markov backend did not return step maps") + correlation = superoperator_period_correlation( + markov.step_maps, + markov.density_matrices[:-1], + coupling, + cfg.period / steps_per_period, + steps_per_period * correlation_periods, + cfg.drive_frequency, + ) + frequencies = np.linspace(0, 3.0, 601) + heat = heat_current_spectrum(correlation, bath, frequencies) + records = [ + {"frequency": float(frequency), "continuous": float(value)} + for frequency, value in zip(heat.frequencies, heat.continuous, strict=True) + ] + return { + "method": heat.method, + "converged": markov.converged, + "diagnostics": { + **markov.diagnostics, + **heat.metadata, + "continuous_tail_amplitude": float(abs(correlation.connected[-1])), + }, + "model": asdict(cfg), + "bath": asdict(bath), + "delta_peaks": [asdict(peak) for peak in heat.delta_peaks], + "data": records, + } diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/floquet.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/floquet.py new file mode 100644 index 000000000..092cb152c --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/floquet.py @@ -0,0 +1,79 @@ +"""Direct finite-dimensional Floquet propagation.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +import numpy as np +from numpy.typing import NDArray +from scipy.linalg import expm + +from .operators import ComplexMatrix + + +@dataclass(frozen=True) +class FloquetSolution: + propagator: ComplexMatrix + quasienergies: NDArray[np.float64] + modes: ComplexMatrix + period: float + step_propagators: tuple[ComplexMatrix, ...] + unitarity_residual: float + eigen_residual: float + + +def one_period_propagator( + hamiltonian: Callable[[float], ComplexMatrix], + period: float, + steps: int, +) -> tuple[ComplexMatrix, tuple[ComplexMatrix, ...]]: + if period <= 0 or steps < 1: + raise ValueError("period and steps must be positive") + dt = period / steps + dimension = hamiltonian(0.0).shape[0] + total = np.eye(dimension, dtype=np.complex128) + pieces: list[ComplexMatrix] = [] + for index in range(steps): + midpoint = (index + 0.5) * dt + piece = expm(-1j * hamiltonian(midpoint) * dt) + pieces.append(piece) + total = piece @ total + return total, tuple(pieces) + + +def solve_floquet( + hamiltonian: Callable[[float], ComplexMatrix], + period: float, + steps: int, +) -> FloquetSolution: + total, pieces = one_period_propagator(hamiltonian, period, steps) + values, modes = np.linalg.eig(total) + phases = np.angle(values) + quasienergies = -phases / period + order = np.argsort(quasienergies) + quasienergies = quasienergies[order].astype(np.float64) + modes = modes[:, order] + modes /= np.linalg.norm(modes, axis=0) + identity = np.eye(total.shape[0]) + unitarity = float(np.linalg.norm(total.conj().T @ total - identity)) + residual = float( + np.linalg.norm(total @ modes - modes * np.exp(-1j * quasienergies * period)) + ) + return FloquetSolution( + total, + quasienergies, + modes, + period, + pieces, + unitarity, + residual, + ) + + +def micromotion(solution: FloquetSolution) -> tuple[ComplexMatrix, ...]: + """Return cumulative propagators at all step boundaries, including t=0.""" + values = [np.eye(solution.propagator.shape[0], dtype=np.complex128)] + for piece in solution.step_propagators: + values.append(piece @ values[-1]) + return tuple(values) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/heat_current.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/heat_current.py new file mode 100644 index 000000000..5df62f44c --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/heat_current.py @@ -0,0 +1,93 @@ +"""Frequency-resolved bath heat current from collective correlations.""" + +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np +from numpy.typing import NDArray +from scipy.integrate import trapezoid + +from .bath import bose_occupation, ohmic_spectral_density +from .config import BathConfig +from .correlations import CorrelationResult + + +@dataclass(frozen=True) +class HeatDeltaPeak: + harmonic: int + frequency: float + weight: float + + +@dataclass(frozen=True) +class HeatCurrentResult: + frequencies: NDArray[np.float64] + continuous: NDArray[np.float64] + delta_peaks: tuple[HeatDeltaPeak, ...] + method: str + metadata: dict[str, float | str] + + +def heat_current_spectrum( + correlation: CorrelationResult, + bath: BathConfig, + frequencies: NDArray[np.float64], + window: str = "hann", +) -> HeatCurrentResult: + """Evaluate the half-sided heat-current transform. + + The continuous part uses only the connected correlation. Coherent + contributions are returned as analytic delta weights. + """ + if np.any(frequencies < 0): + raise ValueError("bath frequencies must be nonnegative") + delays = correlation.delays + if len(delays) < 2 or not np.allclose(np.diff(delays), np.diff(delays)[0]): + raise ValueError("correlation delays must form a uniform grid") + if window == "hann": + taper = np.hanning(2 * len(delays) - 1)[len(delays) - 1 :] + elif window == "none": + taper = np.ones(len(delays)) + else: + raise ValueError("window must be 'hann' or 'none'") + connected = correlation.connected * taper + output = np.zeros_like(frequencies, dtype=np.float64) + for index, frequency in enumerate(frequencies): + if frequency == 0: + continue + occupation = bose_occupation(float(frequency), bath.temperature) + kernel = ( + np.cos(frequency * delays) * np.real(connected) + + (1 + 2 * occupation) + * np.sin(frequency * delays) + * np.imag(connected) + ) + prefactor = 2 * float(ohmic_spectral_density(float(frequency), bath)) * frequency + output[index] = prefactor * trapezoid(kernel, delays) + + delta_peaks = tuple( + HeatDeltaPeak( + peak.harmonic, + peak.frequency, + float( + np.pi + * ohmic_spectral_density(peak.frequency, bath) + * peak.frequency + * peak.correlation_weight + ), + ) + for peak in correlation.delta_peaks + if peak.frequency > 0 + ) + return HeatCurrentResult( + frequencies, + output, + delta_peaks, + correlation.method, + { + "window": window, + "tau_max": float(delays[-1]), + "frequency_resolution": float(2 * np.pi / delays[-1]), + }, + ) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/heat_valve.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/heat_valve.py new file mode 100644 index 000000000..d178aac2c --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/heat_valve.py @@ -0,0 +1,502 @@ +"""Fixed-frequency coherent-destruction scans for the Floquet heat valve.""" + +from __future__ import annotations + +import subprocess +from collections.abc import Iterable +from dataclasses import asdict, dataclass +from typing import Any, Literal + +import numpy as np +from scipy.integrate import trapezoid +from scipy.optimize import linear_sum_assignment +from scipy.special import jn_zeros, jv + +from .backends.uniform_tempo import ( + UNIFORM_TEMPO_REVISION, + UniformTempoBackend, + UniformTempoControls, +) +from .config import BathConfig, ModelConfig +from .convergence import ConvergenceCache, fingerprint +from .dark_channels import floquet_matrix_elements +from .floquet import FloquetSolution, solve_floquet +from .heat_current import heat_current_spectrum +from .models import coupling_operator, drive_operator, ising_hamiltonian +from .operators import ComplexMatrix +from .poles import fit_pole_residues, transfer_poles +from .symmetry import Sector, n2_sectors, n3_reflection_sectors, project + +BESSEL_ROOT = float(jn_zeros(0, 1)[0]) + + +@dataclass(frozen=True) +class HeatValvePoint: + n: Literal[1, 2, 3] + xi: float + j: float = 1.0 + omega: float = 1.0 + drive_frequency: float = 3.0 + alpha: float = 0.05 + cutoff: float = 2.5 + temperature: float = 0.0 + floquet_steps: int = 360 + + def __post_init__(self) -> None: + if self.n not in (1, 2, 3): + raise ValueError("n must be one of 1, 2, 3") + if self.xi < 0: + raise ValueError("xi must be nonnegative") + if self.floquet_steps < 8: + raise ValueError("floquet_steps must be at least eight") + + @property + def drive_amplitude(self) -> float: + return self.xi * self.drive_frequency / 2 + + +@dataclass(frozen=True) +class PreparedHeatValve: + point: HeatValvePoint + model: ModelConfig + bath: BathConfig + sector: Sector + h0: ComplexMatrix + coupling: ComplexMatrix + drive: ComplexMatrix + cat_plus: np.ndarray[Any, np.dtype[np.complex128]] + cat_minus: np.ndarray[Any, np.dtype[np.complex128]] + + +@dataclass(frozen=True) +class ValveNumerics: + steps_per_period: int = 60 + tolerance: float = 1e-6 + phase_samples: int = 3 + delay_periods: int = 12 + pole_count: int = 8 + pole_tolerance: float = 1e-10 + pole_maxiter: int = 2_000 + frequency_max: float = 6.0 + frequency_points: int = 601 + auto_nc: bool = True + memory_cutoff: int = 100_000 + low_rank_svd: bool = False + truncation: Literal["rel", "abs"] = "rel" + cap_rank: int = 100_000 + max_rank: int = 100_000 + + def __post_init__(self) -> None: + if self.steps_per_period < 2: + raise ValueError("steps_per_period must be at least two") + if ( + self.phase_samples < 2 + or self.phase_samples > self.steps_per_period + or self.steps_per_period % self.phase_samples != 0 + ): + raise ValueError("phase_samples must divide steps_per_period") + if not 0 < self.tolerance < 1: + raise ValueError("tolerance must lie between zero and one") + if self.delay_periods < 1: + raise ValueError("delay_periods must be positive") + if self.pole_count < 2: + raise ValueError("pole_count must include steady and decaying poles") + if self.delay_periods + 1 <= self.pole_count - 1: + raise ValueError("delay window must overdetermine the pole fit") + if self.pole_tolerance <= 0 or self.pole_maxiter < 1: + raise ValueError("invalid pole solver controls") + if self.frequency_max <= 0 or self.frequency_points < 2: + raise ValueError("invalid heat-current frequency grid") + if self.memory_cutoff < 1: + raise ValueError("memory_cutoff must be positive") + if self.truncation not in ("rel", "abs"): + raise ValueError("truncation must be 'rel' or 'abs'") + if self.cap_rank < 1 or self.max_rank < self.cap_rank: + raise ValueError("invalid rank limits") + + +def _active_sector(n: int) -> Sector: + if n == 1: + return Sector("full", np.eye(2, dtype=np.complex128)) + if n == 2: + _, triplet = n2_sectors() + return triplet + _, even = n3_reflection_sectors() + return even + + +def _projected_cat( + n: int, + sector: Sector, + sign: Literal[-1, 1], +) -> np.ndarray[Any, np.dtype[np.complex128]]: + up = np.zeros(2**n, dtype=np.complex128) + down = np.zeros(2**n, dtype=np.complex128) + up[0] = 1 + down[-1] = 1 + value = sector.isometry.conj().T @ ((up + sign * down) / np.sqrt(2)) + norm = float(np.linalg.norm(value)) + if norm <= 1e-13: + raise ValueError("selected sector does not contain the cat state") + return np.asarray(value / norm, dtype=np.complex128) + + +def prepare_heat_valve_point(point: HeatValvePoint) -> PreparedHeatValve: + """Project a size point while keeping bath and drive normalizations separate.""" + model = ModelConfig( + n=point.n, + j=point.j, + omega=point.omega, + drive_amplitude=point.drive_amplitude, + drive_frequency=point.drive_frequency, + normalization="bounded", + drive_normalization="per_spin", + ) + bath = BathConfig(point.alpha, point.cutoff, point.temperature) + sector = _active_sector(point.n) + return PreparedHeatValve( + point=point, + model=model, + bath=bath, + sector=sector, + h0=project(ising_hamiltonian(model), sector), + coupling=project(coupling_operator(model), sector), + drive=project(drive_operator(model), sector), + cat_plus=_projected_cat(point.n, sector, +1), + cat_minus=_projected_cat(point.n, sector, -1), + ) + + +def _cat_modes( + prepared: PreparedHeatValve, + solution: FloquetSolution, +) -> tuple[tuple[int, int], tuple[float, float], float]: + cat_basis = np.column_stack((prepared.cat_plus, prepared.cat_minus)) + subspace_weights = np.sum( + abs(cat_basis.conj().T @ solution.modes) ** 2, + axis=0, + ) + selected = np.argsort(subspace_weights)[-2:] + overlaps = abs(cat_basis.conj().T @ solution.modes[:, selected]) ** 2 + rows, columns = linear_sum_assignment(-overlaps) + assigned = { + int(row): (int(selected[column]), float(overlaps[row, column])) + for row, column in zip(rows, columns, strict=True) + } + modes = (assigned[0][0], assigned[1][0]) + direct_overlaps = (assigned[0][1], assigned[1][1]) + cat_overlap = float(np.min(subspace_weights[selected])) + return modes, direct_overlaps, cat_overlap + + +def _isolated_point(point: HeatValvePoint) -> dict[str, Any]: + prepared = prepare_heat_valve_point(point) + model = prepared.model + + def hamiltonian(time: float) -> ComplexMatrix: + return np.asarray( + prepared.h0 + + model.drive_amplitude + * np.cos(model.drive_frequency * time) + * prepared.drive, + dtype=np.complex128, + ) + + solution = solve_floquet( + hamiltonian, + model.period, + point.floquet_steps, + ) + modes, direct_overlaps, cat_overlap = _cat_modes(prepared, solution) + raw_gap = float(abs(solution.quasienergies[modes[0]] - solution.quasienergies[modes[1]])) + cat_gap = min(raw_gap, model.drive_frequency - raw_gap) + transitions = floquet_matrix_elements( + solution, + prepared.coupling, + harmonic_cutoff=3, + ) + selected_modes = set(modes) + cat_brightness = float( + sum( + record.weight + for record in transitions + if record.source in selected_modes + and record.target in selected_modes + and record.source != record.target + ) + ) + return { + **asdict(point), + "drive_amplitude": model.drive_amplitude, + "bessel_j0": float(jv(0, point.xi)), + "bessel_root": BESSEL_ROOT, + "sector": prepared.sector.name, + "dimension": prepared.sector.dimension, + "cat_mode_indices": list(modes), + "cat_plus_overlap": direct_overlaps[0], + "cat_minus_overlap": direct_overlaps[1], + "cat_overlap": cat_overlap, + "cat_gap": float(cat_gap), + "cat_brightness": cat_brightness, + "unitarity_residual": solution.unitarity_residual, + "floquet_eigen_residual": solution.eigen_residual, + } + + +def isolated_valve_scan( + points: Iterable[HeatValvePoint], +) -> dict[str, Any]: + """Run a deterministic exact scan before selecting open-system points.""" + selected = tuple(points) + if not selected: + raise ValueError("the isolated valve scan requires at least one point") + records = [_isolated_point(point) for point in selected] + return { + "complete": True, + "method": "isolated_floquet_coherent_destruction", + "schema_version": 1, + "bessel_root": BESSEL_ROOT, + "points": records, + } + + +def _complex_values(values: np.ndarray[Any, np.dtype[np.complex128]]) -> dict[str, Any]: + array = np.asarray(values, dtype=np.complex128) + return { + "real": np.real(array).astype(float).tolist(), + "imag": np.imag(array).astype(float).tolist(), + } + + +def _git_commit() -> str: + try: + return subprocess.check_output( + ["git", "rev-parse", "HEAD"], + text=True, + stderr=subprocess.DEVNULL, + ).strip() + except (OSError, subprocess.CalledProcessError): + return "unknown" + + +def run_uniform_valve_point( + point: HeatValvePoint, + numerics: ValveNumerics, + cache: ConvergenceCache, + *, + source_revision: str | None = None, +) -> dict[str, Any]: + """Run or restore one pole-resolved UniformTEMPO heat-valve point.""" + prepared = prepare_heat_valve_point(point) + revision = _git_commit() if source_revision is None else source_revision + cache_payload = { + "experiment": "uniform-floquet-pole-heat-valve-v1", + "point": asdict(point), + "numerics": asdict(numerics), + "h0": _complex_values(prepared.h0), + "coupling": _complex_values(prepared.coupling), + "drive": _complex_values(prepared.drive), + "bath": asdict(prepared.bath), + "uniform_tempo_revision": UNIFORM_TEMPO_REVISION, + } + key = fingerprint(cache_payload, revision) + if cache.contains(key): + return cache.load(key) + + controls = UniformTempoControls( + steps_per_period=numerics.steps_per_period, + tolerance=numerics.tolerance, + phase_samples=numerics.phase_samples, + delay_periods=numerics.delay_periods, + auto_nc=numerics.auto_nc, + memory_cutoff=numerics.memory_cutoff, + low_rank_svd=numerics.low_rank_svd, + truncation=numerics.truncation, + cap_rank=numerics.cap_rank, + max_rank=numerics.max_rank, + pole_count=numerics.pole_count, + pole_tolerance=numerics.pole_tolerance, + pole_maxiter=numerics.pole_maxiter, + ) + run = UniformTempoBackend( + tensor_cache_directory=cache.directory / "process_tensors" + ).run_periodic( + prepared.h0, + prepared.coupling, + prepared.model, + prepared.bath, + controls, + drive_operator=prepared.drive, + ) + poles = transfer_poles( + run.transfer_eigenvalues, + run.transfer_eigenpair_residuals, + prepared.model.period, + ) + fit = fit_pole_residues( + poles, + run.correlation.delays, + run.correlation.connected, + prepared.model.period, + max_modes=len(poles), + ) + frequencies = np.linspace( + 0, + numerics.frequency_max, + numerics.frequency_points, + ) + heat = heat_current_spectrum( + run.correlation, + prepared.bath, + frequencies, + ) + connected_tail = float(abs(run.correlation.connected[-1])) + maximum_eigenpair_residual = float( + np.max(run.transfer_eigenpair_residuals) + ) + maximum_pole_modulus = float( + max(abs(item.eigenvalue) for item in poles) + ) + converged = bool( + run.diagnostics["trace_error"] <= 5e-3 + and run.diagnostics["hermiticity_error"] <= 5e-3 + and run.diagnostics["minimum_density_eigenvalue"] >= -5e-3 + and run.diagnostics["fixed_point_residual"] <= 1e-3 + and connected_tail <= 5e-2 + and maximum_eigenpair_residual <= 1e-8 + and maximum_pole_modulus <= 1 + 1e-6 + and fit.reconstruction_residual <= 5e-2 + ) + residue_records = [ + { + "eigenvalue": { + "real": float(item.pole.eigenvalue.real), + "imag": float(item.pole.eigenvalue.imag), + "abs": float(abs(item.pole.eigenvalue)), + }, + "decay_rate": item.pole.decay_rate, + "quasifrequency": item.pole.quasifrequency, + "eigenpair_residual": item.pole.eigenpair_residual, + "residue": { + "real": float(item.residue.real), + "imag": float(item.residue.imag), + "abs": float(abs(item.residue)), + }, + } + for item in fit.residues + ] + dominant = max(fit.residues, key=lambda item: abs(item.residue)) + payload: dict[str, Any] = { + "method": run.method, + "source_commit": revision, + "point": asdict(point), + "model": asdict(prepared.model), + "bath": asdict(prepared.bath), + "numerics": asdict(numerics), + "sector": prepared.sector.name, + "dimension": prepared.sector.dimension, + "converged": converged, + "diagnostics": { + **run.diagnostics, + **run.metadata, + "connected_tail": connected_tail, + "maximum_eigenpair_residual": maximum_eigenpair_residual, + "maximum_pole_modulus": maximum_pole_modulus, + }, + "phase_state": _complex_values(run.floquet_state), + "phase_states": _complex_values(run.phase_states), + "correlation": { + "delay": run.correlation.delays.tolist(), + "total": _complex_values(run.correlation.total), + "connected": _complex_values(run.correlation.connected), + "coherent": run.correlation.coherent.tolist(), + }, + "frequency": heat.frequencies.tolist(), + "continuous": heat.continuous.tolist(), + "delta_peaks": [asdict(item) for item in heat.delta_peaks], + "integrated_absolute_heat": float( + trapezoid(abs(heat.continuous), heat.frequencies) + ), + "poles": residue_records, + "dominant_residue": { + "real": float(dominant.residue.real), + "imag": float(dominant.residue.imag), + "abs": float(abs(dominant.residue)), + }, + "visible_residue_weight": float( + sum(abs(item.residue) for item in fit.residues) + ), + "pole_fit": { + "reconstruction": _complex_values(fit.reconstruction), + "stroboscopic_delays": fit.stroboscopic_delays.tolist(), + "reconstruction_residual": fit.reconstruction_residual, + "condition_number": fit.condition_number, + }, + } + cache.store(key, payload) + return cache.load(key) + + +def _point_from_scan(record: dict[str, Any]) -> HeatValvePoint: + return HeatValvePoint( + n=int(record["n"]), # type: ignore[arg-type] + xi=float(record["xi"]), + j=float(record["j"]), + omega=float(record["omega"]), + drive_frequency=float(record["drive_frequency"]), + alpha=float(record["alpha"]), + cutoff=float(record["cutoff"]), + temperature=float(record["temperature"]), + floquet_steps=int(record["floquet_steps"]), + ) + + +def build_heat_valve_manifest( + isolated_scan: dict[str, Any], + uniform_results: Iterable[dict[str, Any]] = (), +) -> dict[str, Any]: + """Select two-sided valve points and combine any completed exact results.""" + scan_records = tuple(isolated_scan.get("points", ())) + selected: list[HeatValvePoint] = [] + for n in (1, 2, 3): + candidates = [ + record + for record in scan_records + if int(record["n"]) == n and float(record["cat_overlap"]) >= 0.5 + ] + if not candidates: + raise ValueError(f"isolated scan has no resolved N={n} cat points") + minimum = min(candidates, key=lambda item: float(item["cat_gap"])) + minimum_xi = float(minimum["xi"]) + lower = [ + record + for record in candidates + if float(record["xi"]) <= minimum_xi - 0.15 + ] + upper = [ + record + for record in candidates + if float(record["xi"]) >= minimum_xi + 0.15 + ] + if not lower or not upper: + raise ValueError(f"isolated N={n} minimum lacks two resolved flanks") + selected.extend( + ( + _point_from_scan(max(lower, key=lambda item: float(item["xi"]))), + _point_from_scan(minimum), + _point_from_scan(min(upper, key=lambda item: float(item["xi"]))), + ) + ) + + results = tuple(uniform_results) + result_keys = { + (int(item["point"]["n"]), float(item["point"]["xi"])) for item in results + } + selected_keys = {(item.n, item.xi) for item in selected} + return { + "complete": result_keys == selected_keys, + "method": "uniform-floquet-pole-heat-valve-v1", + "isolated_scan_complete": bool(isolated_scan.get("complete", False)), + "selected_points": [asdict(item) for item in selected], + "points": list(results), + } diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/heat_valve_audit.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/heat_valve_audit.py new file mode 100644 index 000000000..21448dc27 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/heat_valve_audit.py @@ -0,0 +1,198 @@ +"""Independent scientific claim gates for the pole-resolved heat valve.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +import numpy as np + + +@dataclass(frozen=True) +class HeatValveAudit: + complete: bool + dark_channel_passed: bool + many_body_amplification_passed: bool + markov_payoff_passed: bool + failures: tuple[str, ...] + metrics: dict[str, float] + + +def _same_numeric(values: list[float], tolerance: float = 1e-12) -> bool: + return bool(values) and bool( + np.allclose(values, values[0], rtol=tolerance, atol=tolerance) + ) + + +def _ratio(numerator: float, denominator: float) -> float: + if denominator <= 0: + return float("inf") + return numerator / denominator + + +def audit_heat_valve_manifest(manifest: dict[str, Any]) -> HeatValveAudit: + """Apply the design gates without relying on any plotting decisions.""" + dark_failures: list[str] = [] + auxiliary_failures: list[str] = [] + metrics: dict[str, float] = {} + selected = list(manifest.get("selected_points", ())) + points = list(manifest.get("points", ())) + + selected_keys = [ + (int(item["n"]), float(item["xi"])) + for item in selected + if isinstance(item, dict) and "n" in item and "xi" in item + ] + point_map = { + (int(item["point"]["n"]), float(item["point"]["xi"])): item + for item in points + if isinstance(item, dict) + and isinstance(item.get("point"), dict) + and "n" in item["point"] + and "xi" in item["point"] + } + complete = bool( + manifest.get("complete", False) + and len(selected_keys) == 9 + and len(set(selected_keys)) == 9 + and len(point_map) == 9 + and set(selected_keys) == set(point_map) + and all(sum(key[0] == n for key in selected_keys) == 3 for n in (1, 2, 3)) + ) + if not complete: + dark_failures.append("heat-valve manifest is incomplete") + + drive_frequencies = [ + float(item["model"]["drive_frequency"]) + for item in point_map.values() + ] + if point_map and not _same_numeric(drive_frequencies): + dark_failures.append("fixed drive frequency was not held constant") + + fixed_controls = ( + ("j", [float(item["model"]["j"]) for item in point_map.values()]), + ( + "omega", + [float(item["model"]["omega"]) for item in point_map.values()], + ), + ( + "alpha", + [float(item["bath"]["alpha"]) for item in point_map.values()], + ), + ( + "cutoff", + [float(item["bath"]["cutoff"]) for item in point_map.values()], + ), + ( + "temperature", + [float(item["bath"]["temperature"]) for item in point_map.values()], + ), + ) + for label, values in fixed_controls: + if point_map and not _same_numeric(values): + dark_failures.append(f"fixed physical control {label} changed") + for label in ("normalization", "drive_normalization"): + labels = {str(item["model"][label]) for item in point_map.values()} + if point_map and len(labels) != 1: + dark_failures.append(f"fixed physical control {label} changed") + + for key, item in point_map.items(): + label = f"N={key[0]}, xi={key[1]:g}" + if not bool(item.get("converged", False)): + dark_failures.append(f"{label}: convergence gate failed") + diagnostics = item.get("diagnostics", {}) + if ( + float(diagnostics.get("trace_error", float("inf"))) > 5e-3 + or float(diagnostics.get("hermiticity_error", float("inf"))) > 5e-3 + or float( + diagnostics.get("minimum_density_eigenvalue", -float("inf")) + ) + < -5e-3 + or float(diagnostics.get("fixed_point_residual", float("inf"))) + > 1e-3 + or float(diagnostics.get("connected_tail", float("inf"))) > 5e-2 + ): + dark_failures.append(f"{label}: physical diagnostics gate failed") + reconstruction = float( + item.get("pole_fit", {}).get( + "reconstruction_residual", + float("inf"), + ) + ) + if reconstruction > 5e-2: + dark_failures.append(f"{label}: pole reconstruction residual exceeds 5%") + for pole in item.get("poles", ()): + if float(pole.get("eigenpair_residual", float("inf"))) > 1e-8: + dark_failures.append(f"{label}: eigenpair residual exceeds 1e-8") + break + for pole in item.get("poles", ()): + modulus = float( + pole.get("eigenvalue", {}).get("abs", float("inf")) + ) + if modulus > 1 + 1e-6: + dark_failures.append(f"{label}: transfer pole leaves the unit disk") + break + + heat_contrasts: dict[int, float] = {} + for n in (1, 2, 3): + ordered_keys = [key for key in selected_keys if key[0] == n] + if len(ordered_keys) != 3 or any(key not in point_map for key in ordered_keys): + continue + lower, minimum, upper = (point_map[key] for key in ordered_keys) + lower_heat = float(lower.get("integrated_absolute_heat", float("nan"))) + minimum_heat = float(minimum.get("integrated_absolute_heat", float("nan"))) + upper_heat = float(upper.get("integrated_absolute_heat", float("nan"))) + heat_ratio = max( + _ratio(minimum_heat, lower_heat), + _ratio(minimum_heat, upper_heat), + ) + heat_contrast = _ratio(min(lower_heat, upper_heat), minimum_heat) + heat_contrasts[n] = heat_contrast + metrics[f"heat_ratio_n{n}"] = heat_ratio + metrics[f"heat_contrast_n{n}"] = heat_contrast + if not np.isfinite(heat_ratio) or heat_ratio > 0.1: + dark_failures.append( + f"N={n}: tenfold heat suppression against both flanks failed" + ) + + lower_residue = float( + lower.get("visible_residue_weight", float("nan")) + ) + minimum_residue = float( + minimum.get("visible_residue_weight", float("nan")) + ) + upper_residue = float( + upper.get("visible_residue_weight", float("nan")) + ) + residue_ratio = max( + _ratio(minimum_residue, lower_residue), + _ratio(minimum_residue, upper_residue), + ) + metrics[f"residue_ratio_n{n}"] = residue_ratio + if not np.isfinite(residue_ratio) or residue_ratio > 0.1: + dark_failures.append( + f"N={n}: tenfold residue suppression against both flanks failed" + ) + + dark_channel_passed = complete and not dark_failures + amplification = bool( + dark_channel_passed + and 1 in heat_contrasts + and 3 in heat_contrasts + and heat_contrasts[3] >= 5 * heat_contrasts[1] + ) + if dark_channel_passed and not amplification: + auxiliary_failures.append( + "many-body amplification did not exceed the fivefold contrast gate" + ) + markov_payoff = bool( + manifest.get("markov_comparison", {}).get("passed", False) + ) + return HeatValveAudit( + complete=complete, + dark_channel_passed=dark_channel_passed, + many_body_amplification_passed=amplification, + markov_payoff_passed=markov_payoff, + failures=tuple(dark_failures + auxiliary_failures), + metrics=metrics, + ) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/influence.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/influence.py new file mode 100644 index 000000000..ca3a4fbd3 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/influence.py @@ -0,0 +1,71 @@ +"""Discrete Feynman-Vernon coefficients for piecewise-constant paths.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +import numpy as np +from numpy.typing import NDArray +from scipy.integrate import quad + +from .bath import bath_correlation +from .config import BathConfig + + +@dataclass(frozen=True) +class InfluenceCoefficients: + values: NDArray[np.complex128] + dt: float + quadrature_error: float + tail_bound: float + + +def _integrate_complex( + function: Callable[[float], complex], low: float, high: float +) -> tuple[complex, float]: + real, real_error = quad(lambda x: np.real(function(x)), low, high, epsabs=2e-11) + imag, imag_error = quad(lambda x: np.imag(function(x)), low, high, epsabs=2e-11) + return complex(real, imag), float(real_error + imag_error) + + +def discretize_influence( + bath: BathConfig, + dt: float, + memory_steps: int, +) -> InfluenceCoefficients: + """Cell-integrate the bath correlation. + + ``eta[0]`` uses the causal triangle in a single time cell. ``eta[k>0]`` + uses two full cells separated by ``k`` timesteps. + """ + if dt <= 0 or memory_steps < 1: + raise ValueError("dt and memory_steps must be positive") + values = np.empty(memory_steps + 1, dtype=np.complex128) + error = 0.0 + + # eta_0 = integral_0^dt du integral_0^u dv C(u-v) + value0, error0 = _integrate_complex( + lambda tau: (dt - tau) * bath_correlation(tau, bath), 0, dt + ) + values[0] = value0 + error += error0 + + for lag in range(1, memory_steps + 1): + # Difference of two points from equal-width cells has triangular weight. + center = lag * dt + + def integrand(offset: float, center: float = center) -> complex: + return (dt - abs(offset)) * bath_correlation(center + offset, bath) + + value, item_error = _integrate_complex( + integrand, + -dt, + dt, + ) + values[lag] = value + error += item_error + + memory_time = memory_steps * dt + tail_bound = bath.alpha / max(1 / bath.cutoff, memory_time) + return InfluenceCoefficients(values, dt, error, float(tail_bound)) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/io.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/io.py new file mode 100644 index 000000000..481a7bd90 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/io.py @@ -0,0 +1,60 @@ +"""Result serialization and provenance.""" + +from __future__ import annotations + +import hashlib +import json +import platform +import subprocess +from datetime import UTC, datetime +from pathlib import Path +from typing import Any, cast + +import numpy as np + +from . import __version__ + + +def _git_commit() -> str: + try: + return subprocess.check_output( + ["git", "rev-parse", "HEAD"], text=True, stderr=subprocess.DEVNULL + ).strip() + except (OSError, subprocess.CalledProcessError): + return "unknown" + + +def write_result(path: Path, payload: dict[str, Any]) -> None: + canonical = json.dumps(payload, sort_keys=True, default=str) + envelope = { + "schema_version": 1, + "config_hash": hashlib.sha256(canonical.encode()).hexdigest()[:16], + "created_utc": datetime.now(UTC).isoformat(), + "environment": { + "python": platform.python_version(), + "numpy": np.__version__, + "package": __version__, + "git_commit": _git_commit(), + }, + **payload, + } + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(envelope, indent=2, sort_keys=True), encoding="utf-8") + + +def read_result(path: Path) -> dict[str, Any]: + result = json.loads(path.read_text(encoding="utf-8")) + required = { + "schema_version", + "config_hash", + "created_utc", + "environment", + "method", + "converged", + "diagnostics", + "data", + } + missing = required - set(result) + if missing: + raise ValueError(f"{path} is missing keys: {sorted(missing)}") + return cast(dict[str, Any], result) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/model_comparison.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/model_comparison.py new file mode 100644 index 000000000..3a5453819 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/model_comparison.py @@ -0,0 +1,97 @@ +"""Explicit common-bath normalization and counterterm model variants.""" + +from __future__ import annotations + +from dataclasses import asdict, dataclass +from typing import Any + +import numpy as np +from numpy.typing import NDArray + +from .config import BathConfig, ModelConfig, Normalization +from .models import coupling_operator, ising_hamiltonian +from .operators import ComplexMatrix + + +@dataclass(frozen=True) +class ModelVariant: + name: str + config: ModelConfig + metadata: dict[str, Any] + + +def _variant( + *, + name: str, + n: int, + j: float, + omega: float, + drive_amplitude: float, + drive_frequency: float, + normalization: Normalization, + counterterm: bool, + bath: BathConfig, +) -> ModelVariant: + strength = bath.alpha * bath.cutoff if counterterm else 0.0 + config = ModelConfig( + n=n, + j=j, + omega=omega, + drive_amplitude=drive_amplitude, + drive_frequency=drive_frequency, + normalization=normalization, + counterterm=counterterm, + counterterm_strength=strength, + ) + return ModelVariant( + name, + config, + { + "normalization": normalization, + "eta": config.eta, + "counterterm": counterterm, + "counterterm_strength": strength, + "bath": asdict(bath), + }, + ) + + +def model_variants( + *, + n: int, + j: float, + bath: BathConfig, + omega: float = 1.0, + drive_amplitude: float = 0.2, + drive_frequency: float = 1.0, +) -> tuple[ModelVariant, ...]: + """Return the four predeclared bounded/Kac and counterterm choices.""" + return tuple( + _variant( + name=f"{normalization}_{'ct' if counterterm else 'no_ct'}", + n=n, + j=j, + omega=omega, + drive_amplitude=drive_amplitude, + drive_frequency=drive_frequency, + normalization=normalization, + counterterm=counterterm, + bath=bath, + ) + for normalization in ("bounded", "kac") + for counterterm in (False, True) + ) + + +def variant_operators(variant: ModelVariant) -> tuple[ComplexMatrix, ComplexMatrix]: + return ising_hamiltonian(variant.config), coupling_operator(variant.config) + + +def diagnostic_heat_rescaling( + heat: NDArray[np.float64], eta: float +) -> tuple[NDArray[np.float64], NDArray[np.float64]]: + """Return raw heat and a separately labeled eta-squared diagnostic.""" + if eta <= 0: + raise ValueError("eta must be positive") + raw = np.array(heat, dtype=np.float64, copy=True) + return raw, raw / eta**2 diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/models.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/models.py new file mode 100644 index 000000000..40a06c4a2 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/models.py @@ -0,0 +1,38 @@ +"""Hamiltonians for the open-boundary driven transverse-field Ising model.""" + +from __future__ import annotations + +import numpy as np + +from .config import ModelConfig +from .operators import ComplexMatrix, collective_operator, product_operator + + +def coupling_operator(config: ModelConfig) -> ComplexMatrix: + return collective_operator("z", config.n, config.eta) + + +def drive_operator(config: ModelConfig) -> ComplexMatrix: + return collective_operator("z", config.n, config.drive_eta) + + +def ising_hamiltonian(config: ModelConfig) -> ComplexMatrix: + dimension = 2**config.n + hamiltonian = np.zeros((dimension, dimension), dtype=np.complex128) + for site in range(config.n - 1): + hamiltonian -= config.j * product_operator({site: "z", site + 1: "z"}, config.n) + hamiltonian += (config.omega / 2) * collective_operator("x", config.n) + if config.counterterm: + s = coupling_operator(config) + hamiltonian += config.counterterm_strength * (s @ s) + return hamiltonian + + +def driven_hamiltonian(config: ModelConfig, time: float) -> ComplexMatrix: + return np.asarray( + ising_hamiltonian(config) + + config.drive_amplitude + * np.cos(config.drive_frequency * time) + * drive_operator(config), + dtype=np.complex128, + ) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/n2_heat.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/n2_heat.py new file mode 100644 index 000000000..c8bef4b64 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/n2_heat.py @@ -0,0 +1,366 @@ +"""Reusable interacting-triplet N=2 PT-TEMPO heat-current points.""" + +from __future__ import annotations + +import subprocess +from dataclasses import asdict, dataclass +from typing import Any, Literal + +import numpy as np + +from .backends.pt_tempo import PtTempoBackend +from .backends.uniform_tempo import UniformTempoBackend, UniformTempoControls +from .config import BathConfig, ModelConfig, Normalization +from .convergence import ConvergenceCache, fingerprint +from .heat_current import heat_current_spectrum +from .models import coupling_operator, ising_hamiltonian +from .operators import ComplexMatrix +from .spectra import diagonalize, transitions +from .symmetry import n2_sectors, project + + +@dataclass(frozen=True) +class N2HeatPoint: + j: float = 0.5 + backend: Literal["oqupy", "uniform_tempo"] = "oqupy" + omega: float = 1.0 + drive_amplitude: float = 0.2 + drive_ratio: float = 1.0 + drive_frequency: float | None = None + normalization: Normalization = "bounded" + counterterm: bool = False + alpha: float = 0.1 + cutoff: float = 2.5 + temperature: float = 0.0 + steps_per_period: int = 16 + steady_periods: int = 20 + delay_periods: int = 4 + memory_steps: int = 4 + epsrel: float = 1e-5 + frequency_max: float = 3.0 + frequency_points: int = 401 + phase_samples: int = 4 + uniform_auto_nc: bool = True + uniform_memory_cutoff: int = 100_000 + uniform_low_rank_svd: bool = False + uniform_truncation: Literal["rel", "abs"] = "rel" + uniform_cap_rank: int = 100_000 + uniform_max_rank: int = 100_000 + + def __post_init__(self) -> None: + if self.backend not in ("oqupy", "uniform_tempo"): + raise ValueError("backend must be 'oqupy' or 'uniform_tempo'") + if self.drive_ratio <= 0 or self.steps_per_period < 2: + raise ValueError("invalid drive or timestep controls") + if self.steady_periods < 1 or self.delay_periods < 1: + raise ValueError("period counts must be positive") + if self.memory_steps < 1 or not 0 < self.epsrel < 1: + raise ValueError("invalid process-tensor controls") + if ( + self.phase_samples < 2 + or self.phase_samples > self.steps_per_period + or self.steps_per_period % self.phase_samples != 0 + ): + raise ValueError("phase_samples must divide steps_per_period") + if self.uniform_memory_cutoff < 1: + raise ValueError("uniform_memory_cutoff must be positive") + if self.uniform_truncation not in ("rel", "abs"): + raise ValueError("uniform_truncation must be 'rel' or 'abs'") + if ( + self.uniform_cap_rank < 1 + or self.uniform_max_rank < self.uniform_cap_rank + ): + raise ValueError("invalid uniform rank limits") + + +@dataclass(frozen=True) +class PreparedN2: + point: N2HeatPoint + model: ModelConfig + bath: BathConfig + h0: ComplexMatrix + coupling: ComplexMatrix + bright_gap: float + + @property + def dimension(self) -> int: + return int(self.h0.shape[0]) + + +def _bright_gap(hamiltonian: ComplexMatrix, coupling: ComplexMatrix) -> float: + spectrum = diagonalize(hamiltonian) + candidates = [ + item.frequency + for item in transitions(spectrum, spectrum, coupling) + if item.source == 0 and item.frequency > 1e-12 + ] + return float(f"{min(candidates):.14g}") + + +def prepare_n2_triplet(point: N2HeatPoint) -> PreparedN2: + provisional = ModelConfig( + n=2, + j=point.j, + omega=point.omega, + drive_amplitude=point.drive_amplitude, + drive_frequency=point.omega, + normalization=point.normalization, + counterterm=point.counterterm, + counterterm_strength=( + point.alpha * point.cutoff if point.counterterm else 0.0 + ), + ) + _, triplet = n2_sectors() + h0 = project(ising_hamiltonian(provisional), triplet) + coupling = project(coupling_operator(provisional), triplet) + gap = _bright_gap(h0, coupling) + frequency = ( + point.drive_frequency + if point.drive_frequency is not None + else point.drive_ratio * gap + ) + model = ModelConfig( + n=2, + j=point.j, + omega=point.omega, + drive_amplitude=point.drive_amplitude, + drive_frequency=frequency, + normalization=point.normalization, + counterterm=point.counterterm, + counterterm_strength=provisional.counterterm_strength, + ) + return PreparedN2( + point, + model, + BathConfig(point.alpha, point.cutoff, point.temperature), + h0, + coupling, + gap, + ) + + +def _complex_values(values: np.ndarray[Any, np.dtype[np.complex128]]) -> dict[str, Any]: + return { + "real": np.real(values).astype(float).tolist(), + "imag": np.imag(values).astype(float).tolist(), + } + + +def _git_commit() -> str: + try: + return subprocess.check_output( + ["git", "rev-parse", "HEAD"], + text=True, + stderr=subprocess.DEVNULL, + ).strip() + except (OSError, subprocess.CalledProcessError): + return "unknown" + + +def run_n2_heat_point( + point: N2HeatPoint, + cache: ConvergenceCache | None = None, + *, + commit: str | None = None, +) -> dict[str, Any]: + prepared = prepare_n2_triplet(point) + revision = _git_commit() if commit is None else commit + key = fingerprint( + { + "experiment": f"n2_triplet_{point.backend}_heat", + "point": asdict(point), + "model": asdict(prepared.model), + "bath": asdict(prepared.bath), + }, + revision, + ) + if cache is not None and cache.contains(key): + return cache.load(key) + model_hash = fingerprint( + { + "model": asdict(prepared.model), + "bath": asdict(prepared.bath), + "sector": "triplet", + }, + "scientific-model-v1", + ) + projected_model_hash = fingerprint( + { + "h0": _complex_values(prepared.h0), + "coupling": _complex_values(prepared.coupling), + "drive_amplitude": prepared.model.drive_amplitude, + "drive_frequency": prepared.model.drive_frequency, + "bath": asdict(prepared.bath), + }, + "projected-open-system-v1", + ) + if point.backend == "uniform_tempo": + controls = UniformTempoControls( + steps_per_period=point.steps_per_period, + tolerance=point.epsrel, + phase_samples=point.phase_samples, + delay_periods=point.delay_periods, + auto_nc=point.uniform_auto_nc, + memory_cutoff=point.uniform_memory_cutoff, + low_rank_svd=point.uniform_low_rank_svd, + truncation=point.uniform_truncation, + cap_rank=point.uniform_cap_rank, + max_rank=point.uniform_max_rank, + ) + uniform_run = UniformTempoBackend( + tensor_cache_directory=( + None if cache is None else cache.directory / "process_tensors" + ) + ).run_periodic( + prepared.h0, + prepared.coupling, + prepared.model, + prepared.bath, + controls, + ) + correlation = uniform_run.correlation + frequencies = np.linspace( + 0, + point.frequency_max, + point.frequency_points, + ) + heat = heat_current_spectrum(correlation, prepared.bath, frequencies) + connected_tail = float(abs(correlation.connected[-1])) + diagnostics = { + **uniform_run.diagnostics, + **uniform_run.metadata, + "phase_residual": uniform_run.diagnostics["fixed_point_residual"], + "connected_tail_amplitude": connected_tail, + "tau_max": float(correlation.delays[-1]), + "epsrel": point.epsrel, + "phase_samples": point.phase_samples, + } + physical = bool( + uniform_run.diagnostics["trace_error"] <= 5e-3 + and uniform_run.diagnostics["hermiticity_error"] <= 5e-3 + and uniform_run.diagnostics["minimum_density_eigenvalue"] >= -5e-3 + and uniform_run.diagnostics["fixed_point_residual"] <= 1e-3 + and connected_tail <= 5e-2 + ) + uniform_payload: dict[str, Any] = { + "method": uniform_run.method, + "sector": "triplet", + "model": asdict(prepared.model), + "model_hash": model_hash, + "projected_model_hash": projected_model_hash, + "bath": asdict(prepared.bath), + "point": asdict(point), + "bright_gap": prepared.bright_gap, + "dimension": prepared.dimension, + "source_commit": revision, + "converged": physical, + "diagnostics": diagnostics, + "phase_state": _complex_values(uniform_run.floquet_state), + "phase_states": _complex_values(uniform_run.phase_states), + "correlation": { + "delay": correlation.delays.tolist(), + "total": _complex_values(correlation.total), + "connected": _complex_values(correlation.connected), + "coherent": correlation.coherent.tolist(), + }, + "frequency": heat.frequencies.tolist(), + "continuous": heat.continuous.tolist(), + "delta_peaks": [asdict(peak) for peak in heat.delta_peaks], + "fingerprint": key, + } + if cache is not None: + cache.store(key, uniform_payload) + return cache.load(key) + return {"complete": True, **uniform_payload} + + def hamiltonian(time: float) -> ComplexMatrix: + return np.asarray( + prepared.h0 + + prepared.model.drive_amplitude + * np.cos(prepared.model.drive_frequency * time) + * prepared.coupling, + dtype=np.complex128, + ) + + ground = diagonalize(prepared.h0).states[:, 0] + initial = np.outer(ground, ground.conj()) + dt = prepared.model.period / point.steps_per_period + phase_start = point.steady_periods * point.steps_per_period + delay_steps = point.delay_periods * point.steps_per_period + total_steps = phase_start + point.steps_per_period + delay_steps + backend = PtTempoBackend() + run = backend.run( + hamiltonian, + prepared.coupling, + initial, + prepared.bath, + dt, + total_steps, + point.memory_steps, + point.epsrel, + ) + states = run.result.density_matrices + phase_state = states[phase_start] + phase_residual = float( + np.linalg.norm(phase_state - states[phase_start - point.steps_per_period]) + ) + offsets = list( + range(0, point.steps_per_period, point.steps_per_period // point.phase_samples) + ) + correlation = backend.period_averaged_correlation( + run, + prepared.coupling, + phase_start, + point.steps_per_period, + delay_steps, + prepared.model.drive_frequency, + offsets, + ) + frequencies = np.linspace(0, point.frequency_max, point.frequency_points) + heat = heat_current_spectrum(correlation, prepared.bath, frequencies) + diagnostics = { + **run.result.diagnostics, + "phase_residual": phase_residual, + "connected_tail_amplitude": float(abs(correlation.connected[-1])), + "dt": dt, + "tau_max": float(correlation.delays[-1]), + "memory_steps": point.memory_steps, + "epsrel": point.epsrel, + "phase_samples": point.phase_samples, + } + payload: dict[str, Any] = { + "method": "pt_tempo_multitime", + "sector": "triplet", + "model": asdict(prepared.model), + "model_hash": model_hash, + "projected_model_hash": projected_model_hash, + "bath": asdict(prepared.bath), + "point": asdict(point), + "bright_gap": prepared.bright_gap, + "dimension": prepared.dimension, + "source_commit": revision, + "converged": bool( + run.result.converged + and phase_residual < 2e-3 + and float(diagnostics["connected_tail_amplitude"]) < 5e-2 + ), + "diagnostics": diagnostics, + "phase_state": _complex_values(phase_state), + "phase_states": _complex_values( + states[phase_start : phase_start + point.steps_per_period] + ), + "correlation": { + "delay": correlation.delays.tolist(), + "total": _complex_values(correlation.total), + "connected": _complex_values(correlation.connected), + "coherent": correlation.coherent.tolist(), + }, + "frequency": heat.frequencies.tolist(), + "continuous": heat.continuous.tolist(), + "delta_peaks": [asdict(peak) for peak in heat.delta_peaks], + "fingerprint": key, + } + if cache is not None: + cache.store(key, payload) + return cache.load(key) + return {"complete": True, **payload} diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/n3_heat.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/n3_heat.py new file mode 100644 index 000000000..acf7d5a74 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/n3_heat.py @@ -0,0 +1,430 @@ +"""Reusable reflection-resolved N=3 PT-TEMPO heat-current points.""" + +from __future__ import annotations + +import subprocess +from dataclasses import asdict, dataclass +from typing import Any, Literal + +import numpy as np +from numpy.typing import NDArray +from scipy.integrate import trapezoid + +from .backends.pt_tempo import PtTempoBackend +from .backends.uniform_tempo import UniformTempoBackend, UniformTempoControls +from .config import BathConfig, DriveNormalization, ModelConfig, Normalization +from .convergence import ConvergenceCache, fingerprint +from .heat_current import heat_current_spectrum +from .models import coupling_operator, drive_operator, ising_hamiltonian +from .operators import ComplexMatrix +from .spectra import diagonalize, transitions +from .symmetry import Sector, project, reflection_sectors + + +@dataclass(frozen=True) +class N3HeatPoint: + """Complete controls for one reflection-resolved N=3 or N=4 calculation.""" + + n: Literal[3, 4] = 3 + j: float = 0.5 + sector: Literal["even", "odd"] = "even" + backend: Literal["oqupy", "uniform_tempo"] = "oqupy" + omega: float = 1.0 + drive_amplitude: float = 0.2 + drive_ratio: float = 1.0 + drive_frequency: float | None = None + normalization: Normalization = "bounded" + drive_normalization: DriveNormalization = "coupling" + counterterm: bool = False + alpha: float = 0.1 + cutoff: float = 2.5 + temperature: float = 0.0 + steps_per_period: int = 12 + steady_periods: int = 30 + delay_periods: int = 3 + memory_steps: int = 3 + epsrel: float = 1e-5 + frequency_max: float = 3.0 + frequency_points: int = 401 + phase_samples: int | None = None + uniform_auto_nc: bool = True + uniform_memory_cutoff: int = 100_000 + uniform_low_rank_svd: bool = False + uniform_truncation: Literal["rel", "abs"] = "rel" + uniform_cap_rank: int = 100_000 + uniform_max_rank: int = 100_000 + + def __post_init__(self) -> None: + if self.n not in (3, 4): + raise ValueError("n must be 3 or 4") + if self.sector not in ("even", "odd"): + raise ValueError("sector must be 'even' or 'odd'") + if self.backend not in ("oqupy", "uniform_tempo"): + raise ValueError("backend must be 'oqupy' or 'uniform_tempo'") + if self.drive_ratio <= 0: + raise ValueError("drive_ratio must be positive") + if self.steps_per_period < 2 or self.steady_periods < 1: + raise ValueError("insufficient time discretization") + if self.delay_periods < 1 or self.memory_steps < 1: + raise ValueError("delay and memory controls must be positive") + if not 0 < self.epsrel < 1: + raise ValueError("epsrel must lie between zero and one") + if self.frequency_max <= 0 or self.frequency_points < 2: + raise ValueError("invalid frequency grid") + if self.phase_samples is not None and ( + self.phase_samples < 2 + or self.phase_samples > self.steps_per_period + or self.steps_per_period % self.phase_samples != 0 + ): + raise ValueError("phase_samples must divide steps_per_period") + if self.uniform_memory_cutoff < 1: + raise ValueError("uniform_memory_cutoff must be positive") + if self.uniform_truncation not in ("rel", "abs"): + raise ValueError("uniform_truncation must be 'rel' or 'abs'") + if ( + self.uniform_cap_rank < 1 + or self.uniform_max_rank < self.uniform_cap_rank + ): + raise ValueError("invalid uniform rank limits") + + +@dataclass(frozen=True) +class PreparedN3: + point: N3HeatPoint + model: ModelConfig + bath: BathConfig + sector: Sector + h0: ComplexMatrix + coupling: ComplexMatrix + drive: ComplexMatrix + bright_gap: float + + @property + def dimension(self) -> int: + return int(self.h0.shape[0]) + + +def _primary_bright_gap( + hamiltonian: ComplexMatrix, coupling: ComplexMatrix +) -> float: + spectrum = diagonalize(hamiltonian) + records = transitions(spectrum, spectrum, coupling) + candidates = [ + record.frequency + for record in records + if record.source == 0 and record.frequency > 1e-12 + ] + if not candidates: + raise ValueError("sector has no bright transition from its ground state") + # Hermitian eigensolvers may differ in the last one or two binary digits. + # Canonicalizing a derived control prevents scientifically identical jobs + # from receiving different content-addressed cache keys. + return float(f"{min(candidates):.14g}") + + +def prepare_n3_sector(point: N3HeatPoint) -> PreparedN3: + """Project the model exactly and select its primary bright resonance.""" + provisional = ModelConfig( + n=point.n, + j=point.j, + omega=point.omega, + drive_amplitude=point.drive_amplitude, + drive_frequency=point.omega, + normalization=point.normalization, + drive_normalization=point.drive_normalization, + counterterm=point.counterterm, + counterterm_strength=( + point.alpha * point.cutoff if point.counterterm else 0.0 + ), + ) + odd, even = reflection_sectors(point.n) + sector = even if point.sector == "even" else odd + h0 = project(ising_hamiltonian(provisional), sector) + coupling = project(coupling_operator(provisional), sector) + drive = project(drive_operator(provisional), sector) + bright_gap = _primary_bright_gap(h0, coupling) + drive_frequency = ( + point.drive_frequency + if point.drive_frequency is not None + else point.drive_ratio * bright_gap + ) + model = ModelConfig( + n=point.n, + j=point.j, + omega=point.omega, + drive_amplitude=point.drive_amplitude, + drive_frequency=drive_frequency, + normalization=point.normalization, + drive_normalization=point.drive_normalization, + counterterm=point.counterterm, + counterterm_strength=provisional.counterterm_strength, + ) + bath = BathConfig(point.alpha, point.cutoff, point.temperature) + return PreparedN3(point, model, bath, sector, h0, coupling, drive, bright_gap) + + +def _git_commit() -> str: + try: + return subprocess.check_output( + ["git", "rev-parse", "HEAD"], + text=True, + stderr=subprocess.DEVNULL, + ).strip() + except (OSError, subprocess.CalledProcessError): + return "unknown" + + +def _complex_values(values: NDArray[np.complex128]) -> dict[str, list[float]]: + return { + "real": np.real(values).astype(float).tolist(), + "imag": np.imag(values).astype(float).tolist(), + } + + +def run_n3_heat_point( + point: N3HeatPoint, + cache: ConvergenceCache | None = None, + *, + commit: str | None = None, +) -> dict[str, Any]: + """Run or restore one complete PT-TEMPO steady/correlation/heat pipeline.""" + prepared = prepare_n3_sector(point) + revision = _git_commit() if commit is None else commit + key = fingerprint( + { + "experiment": f"n{point.n}_{point.backend}_heat", + "point": asdict(point), + "model": asdict(prepared.model), + "bath": asdict(prepared.bath), + }, + revision, + ) + model_hash = fingerprint( + { + "model": asdict(prepared.model), + "bath": asdict(prepared.bath), + "sector": point.sector, + }, + "scientific-model-v1", + ) + projected_model_hash = fingerprint( + { + "h0": _complex_values(prepared.h0), + "coupling": _complex_values(prepared.coupling), + "drive": _complex_values(prepared.drive), + "drive_amplitude": prepared.model.drive_amplitude, + "drive_frequency": prepared.model.drive_frequency, + "bath": asdict(prepared.bath), + }, + "projected-open-system-v1", + ) + if cache is not None and cache.contains(key): + return cache.load(key) + + model = prepared.model + if point.backend == "uniform_tempo": + phase_samples = ( + point.steps_per_period + if point.phase_samples is None + else point.phase_samples + ) + controls = UniformTempoControls( + steps_per_period=point.steps_per_period, + tolerance=point.epsrel, + phase_samples=phase_samples, + delay_periods=point.delay_periods, + auto_nc=point.uniform_auto_nc, + memory_cutoff=point.uniform_memory_cutoff, + low_rank_svd=point.uniform_low_rank_svd, + truncation=point.uniform_truncation, + cap_rank=point.uniform_cap_rank, + max_rank=point.uniform_max_rank, + ) + uniform_run = UniformTempoBackend( + tensor_cache_directory=( + None if cache is None else cache.directory / "process_tensors" + ) + ).run_periodic( + prepared.h0, + prepared.coupling, + model, + prepared.bath, + controls, + drive_operator=prepared.drive, + ) + correlation = uniform_run.correlation + frequencies = np.linspace( + 0.0, + point.frequency_max, + point.frequency_points, + ) + heat = heat_current_spectrum(correlation, prepared.bath, frequencies) + connected_tail = float(abs(correlation.connected[-1])) + diagnostics = { + **uniform_run.diagnostics, + **uniform_run.metadata, + "phase_residual": uniform_run.diagnostics["fixed_point_residual"], + "connected_tail_amplitude": connected_tail, + "tau_max": float(correlation.delays[-1]), + "epsrel": point.epsrel, + "phase_samples": phase_samples, + } + physical = bool( + uniform_run.diagnostics["trace_error"] <= 5e-3 + and uniform_run.diagnostics["hermiticity_error"] <= 5e-3 + and uniform_run.diagnostics["minimum_density_eigenvalue"] >= -5e-3 + and uniform_run.diagnostics["fixed_point_residual"] <= 1e-3 + and connected_tail <= 5e-2 + ) + uniform_payload: dict[str, Any] = { + "method": uniform_run.method, + "sector": point.sector, + "model": asdict(model), + "model_hash": model_hash, + "projected_model_hash": projected_model_hash, + "bath": asdict(prepared.bath), + "point": asdict(point), + "bright_gap": prepared.bright_gap, + "dimension": prepared.dimension, + "source_commit": revision, + "converged": physical, + "diagnostics": diagnostics, + "phase_state": _complex_values(uniform_run.floquet_state), + "phase_states": _complex_values(uniform_run.phase_states), + "correlation": { + "delay": correlation.delays.tolist(), + "total": _complex_values(correlation.total), + "connected": _complex_values(correlation.connected), + "coherent": correlation.coherent.tolist(), + }, + "frequency": heat.frequencies.tolist(), + "continuous": heat.continuous.tolist(), + "delta_peaks": [asdict(peak) for peak in heat.delta_peaks], + } + uniform_payload["fingerprint"] = key + if cache is not None: + cache.store(key, uniform_payload) + return cache.load(key) + return {"complete": True, **uniform_payload} + + def hamiltonian(time: float) -> ComplexMatrix: + return np.asarray( + prepared.h0 + + model.drive_amplitude + * np.cos(model.drive_frequency * time) + * prepared.drive, + dtype=np.complex128, + ) + + ground = diagonalize(prepared.h0).states[:, 0] + initial = np.outer(ground, ground.conj()) + dt = model.period / point.steps_per_period + phase_start = point.steady_periods * point.steps_per_period + delay_steps = point.delay_periods * point.steps_per_period + total_steps = phase_start + point.steps_per_period + delay_steps + backend = PtTempoBackend() + run = backend.run( + hamiltonian, + prepared.coupling, + initial, + prepared.bath, + dt, + total_steps, + point.memory_steps, + point.epsrel, + ) + states = run.result.density_matrices + phase_state = states[phase_start] + phase_states = states[phase_start : phase_start + point.steps_per_period] + phase_residual = float( + np.linalg.norm(phase_state - states[phase_start - point.steps_per_period]) + ) + correlation = backend.period_averaged_correlation( + run, + prepared.coupling, + phase_start, + point.steps_per_period, + delay_steps, + model.drive_frequency, + ( + None + if point.phase_samples is None + else list( + range( + 0, + point.steps_per_period, + point.steps_per_period // point.phase_samples, + ) + ) + ), + ) + frequencies = np.linspace(0.0, point.frequency_max, point.frequency_points) + heat = heat_current_spectrum(correlation, prepared.bath, frequencies) + connected_tail = float(abs(correlation.connected[-1])) + payload: dict[str, Any] = { + "method": "pt_tempo_multitime", + "sector": point.sector, + "model": asdict(model), + "model_hash": model_hash, + "projected_model_hash": projected_model_hash, + "bath": asdict(prepared.bath), + "point": asdict(point), + "bright_gap": prepared.bright_gap, + "dimension": prepared.dimension, + "source_commit": revision, + "converged": bool( + run.result.converged + and phase_residual < 1e-3 + and connected_tail < 5e-2 + ), + "diagnostics": { + **run.result.diagnostics, + "phase_residual": phase_residual, + "connected_tail_amplitude": connected_tail, + "dt": dt, + "tau_max": float(correlation.delays[-1]), + "memory_steps": point.memory_steps, + "epsrel": point.epsrel, + "phase_samples": ( + point.steps_per_period + if point.phase_samples is None + else point.phase_samples + ), + }, + "phase_state": _complex_values(phase_state), + "phase_states": _complex_values(phase_states), + "correlation": { + "delay": correlation.delays.tolist(), + "total": _complex_values(correlation.total), + "connected": _complex_values(correlation.connected), + "coherent": correlation.coherent.tolist(), + }, + "frequency": heat.frequencies.tolist(), + "continuous": heat.continuous.tolist(), + "delta_peaks": [asdict(peak) for peak in heat.delta_peaks], + } + payload["fingerprint"] = key + if cache is not None: + cache.store(key, payload) + return cache.load(key) + return {"complete": True, **payload} + + +def compare_sector_spectra( + even: dict[str, Any], odd: dict[str, Any] +) -> dict[str, float]: + """Return direct differences without interpolating either spectrum.""" + even_grid = np.asarray(even["frequency"], dtype=float) + odd_grid = np.asarray(odd["frequency"], dtype=float) + if even_grid.shape != odd_grid.shape or not np.allclose(even_grid, odd_grid): + raise ValueError("sector frequency grids do not match") + even_heat = np.asarray(even["continuous"], dtype=float) + odd_heat = np.asarray(odd["continuous"], dtype=float) + difference = even_heat - odd_heat + denominator = float(trapezoid(abs(odd_heat), odd_grid)) + 1e-15 + return { + "maximum_absolute_difference": float(np.max(abs(difference))), + "normalized_l1_difference": float( + trapezoid(abs(difference), even_grid) / denominator + ), + } diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/operators.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/operators.py new file mode 100644 index 000000000..7dd506418 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/operators.py @@ -0,0 +1,70 @@ +"""Spin-half operators with a documented computational-basis convention.""" + +from __future__ import annotations + +import numpy as np +from numpy.typing import NDArray + +ComplexMatrix = NDArray[np.complex128] + +_PAULI: dict[str, ComplexMatrix] = { + "i": np.eye(2, dtype=np.complex128), + "x": np.array([[0, 1], [1, 0]], dtype=np.complex128), + "y": np.array([[0, -1j], [1j, 0]], dtype=np.complex128), + "z": np.array([[1, 0], [0, -1]], dtype=np.complex128), +} + + +def pauli(name: str) -> ComplexMatrix: + try: + return _PAULI[name.lower()].copy() + except KeyError as exc: + raise ValueError(f"unknown Pauli operator {name!r}") from exc + + +def tensor_product(operators: list[ComplexMatrix]) -> ComplexMatrix: + if not operators: + raise ValueError("operators must not be empty") + result = np.array([[1.0 + 0.0j]]) + for operator in operators: + result = np.kron(result, operator) + return result + + +def site_operator(name: str, site: int, n: int) -> ComplexMatrix: + if n < 1 or not 0 <= site < n: + raise ValueError("site must satisfy 0 <= site < n") + factors = [pauli("i") for _ in range(n)] + factors[site] = pauli(name) + return tensor_product(factors) + + +def product_operator(terms: dict[int, str], n: int) -> ComplexMatrix: + factors = [pauli("i") for _ in range(n)] + for site, name in terms.items(): + if not 0 <= site < n: + raise ValueError("site must satisfy 0 <= site < n") + factors[site] = pauli(name) + return tensor_product(factors) + + +def collective_operator(name: str, n: int, eta: float = 1.0) -> ComplexMatrix: + if n < 1: + raise ValueError("n must be positive") + result = np.zeros((2**n, 2**n), dtype=np.complex128) + for site in range(n): + result += site_operator(name, site, n) + return np.asarray(eta * result, dtype=np.complex128) + + +def swap_operator(first: int, second: int, n: int) -> ComplexMatrix: + """Permutation matrix that swaps two spin sites.""" + if first == second or not (0 <= first < n and 0 <= second < n): + raise ValueError("swap sites must be distinct valid indices") + out = np.zeros((2**n, 2**n), dtype=np.complex128) + for column in range(2**n): + bits = list(format(column, f"0{n}b")) + bits[first], bits[second] = bits[second], bits[first] + row = int("".join(bits), 2) + out[row, column] = 1 + return out diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/paper_extension.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/paper_extension.py new file mode 100644 index 000000000..774fa0516 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/paper_extension.py @@ -0,0 +1,1054 @@ +"""Resumable publication-grid orchestration for the N=3 extension.""" + +from __future__ import annotations + +import copy +import json +import math +from dataclasses import asdict, replace +from pathlib import Path +from typing import Any, Literal, cast + +import numpy as np +from scipy.integrate import trapezoid + +from .adaptive import ( + AdaptiveResult, + AdaptiveSchedule, + UniformAdaptiveSchedule, + run_adaptive, + run_uniform_adaptive, + run_uniform_compression_audit, +) +from .backends.floquet_markov import FloquetMarkovBackend +from .convergence import ConvergenceCache, atomic_write_result, fingerprint +from .correlations import superoperator_period_correlation +from .dark_channels import ( + dark_candidates, + floquet_matrix_elements, + harmonic_sum_rule, + period_variance, +) +from .error_map import audit_grid_manifest, build_error_record +from .floquet import solve_floquet +from .heat_current import heat_current_spectrum +from .model_comparison import model_variants +from .n2_heat import N2HeatPoint, prepare_n2_triplet, run_n2_heat_point +from .n3_heat import N3HeatPoint, prepare_n3_sector, run_n3_heat_point + +ENGINE_REVISION = "215cc9dbab236d77e0a89e276e3d1ff2b0e26d1f" +N2_ENGINE_REVISION = "f995eace0d7939b34b599cfbce6baef235f66f7f" +PHASE_QUADRATURE_EVIDENCE = { + "model": "N3 reflection-even, J=0.5, M=12, K=3, epsrel=1e-5", + "full_phase_fingerprint": ( + "b1903593b5ebdbfd0257697086531cf6ff4701fd2deb1153e97a1bb3aa625a0d" + ), + "three_phase_fingerprint": ( + "50eeb896cbb9385cb96da4731544cb11a7c2567780709562fe07af2759a07455" + ), + "correlation_residual": 2.140601839862832e-4, + "heat_residual": 6.878849326613947e-4, + "maximum_absolute_heat_difference": 3.707806353551828e-5, +} +ExactBackend = Literal["uniform_tempo", "oqupy"] + + +def publication_schedule() -> AdaptiveSchedule: + return AdaptiveSchedule( + memory_steps=(3, 4, 5), + steps_per_period=(12, 18), + epsrel=(1e-5, 3e-6), + state_threshold=8e-2, + correlation_threshold=8e-2, + heat_threshold=8e-2, + phase_threshold=2e-3, + trace_threshold=5e-3, + ) + + +def uniform_publication_schedule() -> UniformAdaptiveSchedule: + return UniformAdaptiveSchedule( + steps_per_period=(60, 90, 120), + tolerances=(3e-7, 1e-7, 3e-8), + phase_samples=(3, 15), + state_threshold=5e-2, + correlation_threshold=8e-2, + heat_threshold=8e-2, + phase_threshold=1e-3, + trace_threshold=5e-3, + hermiticity_threshold=5e-3, + ) + + +def uniform_error_schedule() -> UniformAdaptiveSchedule: + """Use a deeper compression ladder for the inexpensive N=2 error grid.""" + return replace( + uniform_publication_schedule(), + tolerances=(3e-7, 1e-7, 3e-8, 1e-8, 3e-9), + ) + + +def n4_pilot_schedule() -> UniformAdaptiveSchedule: + """A bounded two-rung gate before spending publication-scale N=4 resources.""" + return UniformAdaptiveSchedule( + steps_per_period=(30, 60), + tolerances=(1e-5, 3e-6), + phase_samples=(3, 15), + state_threshold=8e-2, + correlation_threshold=1e-1, + heat_threshold=1e-1, + phase_threshold=2e-3, + trace_threshold=5e-3, + hermiticity_threshold=5e-3, + ) + + +def n2_correlation_delay_periods(alpha: float) -> int: + """Choose a weak-coupling correlation window with a fixed decay budget.""" + if alpha <= 0: + raise ValueError("alpha must be positive") + return max(4, math.ceil(0.3 / alpha)) + + +def _runner(point: N3HeatPoint, cache: ConvergenceCache | None) -> dict[str, Any]: + result = run_n3_heat_point( + point, + cache, + commit=ENGINE_REVISION if point.backend == "oqupy" else None, + ) + if point.backend == "uniform_tempo": + return result + diagnostics = result["diagnostics"] + accepted = ( + float(diagnostics["trace_error"]) < 2e-2 + and float(diagnostics["minimum_density_eigenvalue"]) > -5e-2 + and float(diagnostics["phase_residual"]) < 1e-2 + and float(diagnostics["connected_tail_amplitude"]) < 1e-1 + ) + return {**result, "converged": accepted} + + +def _n2_runner(point: N2HeatPoint, cache: ConvergenceCache | None) -> dict[str, Any]: + result = run_n2_heat_point( + point, + cache, + commit=N2_ENGINE_REVISION if point.backend == "oqupy" else None, + ) + if point.backend == "uniform_tempo": + return result + diagnostics = result["diagnostics"] + accepted = ( + float(diagnostics["trace_error"]) < 2e-2 + and float(diagnostics["minimum_density_eigenvalue"]) > -5e-2 + and float(diagnostics["phase_residual"]) < 1e-2 + and float(diagnostics["connected_tail_amplitude"]) < 1e-1 + ) + return {**result, "converged": accepted} + + +def n2_publication_schedule() -> AdaptiveSchedule: + return AdaptiveSchedule( + memory_steps=(4, 5, 6), + steps_per_period=(16, 24), + epsrel=(1e-5, 3e-6), + state_threshold=8e-2, + correlation_threshold=8e-2, + heat_threshold=8e-2, + phase_threshold=2e-3, + trace_threshold=5e-3, + ) + + +def _adaptive_payload(result: AdaptiveResult) -> dict[str, Any]: + payload = dict(result.final_result) + payload["adaptive_status"] = result.status + payload["adaptive_converged"] = result.converged + payload["evidence"] = [asdict(item) for item in result.evidence] + payload["final_point"] = asdict(result.final_point) + payload["failed_parameter"] = result.failed_parameter + return payload + + +def _odd_equivalent_payload( + reference: dict[str, Any], + j: float, +) -> dict[str, Any]: + """Reuse an odd-sector record after proving the projected model is identical.""" + payload = copy.deepcopy(reference) + point = N3HeatPoint(**payload["final_point"]) + target_point = replace(point, j=j) + prepared_reference = prepare_n3_sector(point) + prepared_target = prepare_n3_sector(target_point) + h0_residual = float(np.linalg.norm(prepared_reference.h0 - prepared_target.h0)) + coupling_residual = float( + np.linalg.norm(prepared_reference.coupling - prepared_target.coupling) + ) + drive_residual = float( + np.linalg.norm(prepared_reference.drive - prepared_target.drive) + ) + if ( + h0_residual > 1e-13 + or coupling_residual > 1e-13 + or drive_residual > 1e-13 + ): + raise ValueError("odd-sector projected models are not equivalent") + + source_fingerprint = str(reference["fingerprint"]) + payload["point"]["j"] = j + payload["final_point"]["j"] = j + payload["model"]["j"] = j + payload["bright_gap"] = prepared_target.bright_gap + payload["model_hash"] = fingerprint( + { + "model": asdict(prepared_target.model), + "bath": asdict(prepared_target.bath), + "sector": "odd", + }, + "scientific-model-v1", + ) + payload["projected_model_hash"] = reference["projected_model_hash"] + payload["fingerprint"] = fingerprint( + { + "kind": "exact-odd-sector-equivalence", + "source_fingerprint": source_fingerprint, + "target_j": j, + "projected_model_hash": payload["projected_model_hash"], + }, + str(reference.get("source_commit", "unknown")), + ) + payload["numerical_reuse"] = { + "reason": "reflection-odd projected Hamiltonian and coupling are J-independent", + "source_j": point.j, + "source_fingerprint": source_fingerprint, + "h0_frobenius_residual": h0_residual, + "coupling_frobenius_residual": coupling_residual, + "drive_frobenius_residual": drive_residual, + } + return payload + + +def _complex_values(values: np.ndarray[Any, np.dtype[np.complex128]]) -> dict[str, Any]: + return { + "real": np.real(values).astype(float).tolist(), + "imag": np.imag(values).astype(float).tolist(), + } + + +def _complex_array(value: dict[str, Any]) -> np.ndarray[Any, np.dtype[np.complex128]]: + return cast( + np.ndarray[Any, np.dtype[np.complex128]], + np.asarray(value["real"], dtype=float) + + 1j * np.asarray(value["imag"], dtype=float), + ) + + +def _markov_result(point: N3HeatPoint, model_hash: str) -> dict[str, Any]: + prepared = prepare_n3_sector(point) + model = prepared.model + + def hamiltonian(time: float) -> np.ndarray[Any, np.dtype[np.complex128]]: + return np.asarray( + prepared.h0 + + model.drive_amplitude + * np.cos(model.drive_frequency * time) + * prepared.drive, + dtype=np.complex128, + ) + + backend = FloquetMarkovBackend() + run = backend.run( + hamiltonian, + prepared.coupling, + prepared.bath, + model.period, + point.steps_per_period, + harmonic_cutoff=5, + ) + if run.step_maps is None: + raise RuntimeError("Floquet-Markov backend returned no step maps") + delay_steps = point.delay_periods * point.steps_per_period + correlation = superoperator_period_correlation( + run.step_maps, + run.density_matrices[:-1], + prepared.coupling, + model.period / point.steps_per_period, + delay_steps, + model.drive_frequency, + ) + frequencies = np.linspace(0, point.frequency_max, point.frequency_points) + heat = heat_current_spectrum(correlation, prepared.bath, frequencies) + return { + "method": "floquet_markov_qr", + "converged": run.converged, + "model_hash": model_hash, + "model": asdict(model), + "bath": asdict(prepared.bath), + "diagnostics": run.diagnostics, + "phase_state": _complex_values(run.density_matrices[0]), + "correlation": { + "delay": correlation.delays.tolist(), + "connected": _complex_values(correlation.connected), + }, + "frequency": heat.frequencies.tolist(), + "continuous": heat.continuous.tolist(), + "delta_peaks": [asdict(peak) for peak in heat.delta_peaks], + } + + +def _markov_n2_result(point: N2HeatPoint, model_hash: str) -> dict[str, Any]: + prepared = prepare_n2_triplet(point) + model = prepared.model + + def hamiltonian(time: float) -> np.ndarray[Any, np.dtype[np.complex128]]: + return np.asarray( + prepared.h0 + + model.drive_amplitude + * np.cos(model.drive_frequency * time) + * prepared.coupling, + dtype=np.complex128, + ) + + backend = FloquetMarkovBackend() + run = backend.run( + hamiltonian, + prepared.coupling, + prepared.bath, + model.period, + point.steps_per_period, + harmonic_cutoff=5, + ) + if run.step_maps is None: + raise RuntimeError("Floquet-Markov backend returned no step maps") + delay_steps = point.delay_periods * point.steps_per_period + correlation = superoperator_period_correlation( + run.step_maps, + run.density_matrices[:-1], + prepared.coupling, + model.period / point.steps_per_period, + delay_steps, + model.drive_frequency, + ) + frequencies = np.linspace(0, point.frequency_max, point.frequency_points) + heat = heat_current_spectrum(correlation, prepared.bath, frequencies) + return { + "method": "floquet_markov_qr", + "converged": run.converged, + "model_hash": model_hash, + "model": asdict(model), + "bath": asdict(prepared.bath), + "diagnostics": run.diagnostics, + "phase_state": _complex_values(run.density_matrices[0]), + "correlation": { + "delay": correlation.delays.tolist(), + "connected": _complex_values(correlation.connected), + }, + "frequency": heat.frequencies.tolist(), + "continuous": heat.continuous.tolist(), + "delta_peaks": [asdict(peak) for peak in heat.delta_peaks], + } + + +def _dark_diagnostic(result: dict[str, Any]) -> dict[str, Any]: + point = N3HeatPoint(**result["final_point"]) + prepared = prepare_n3_sector(point) + + def hamiltonian(time: float) -> np.ndarray[Any, np.dtype[np.complex128]]: + return np.asarray( + prepared.h0 + + prepared.model.drive_amplitude + * np.cos(prepared.model.drive_frequency * time) + * prepared.drive, + dtype=np.complex128, + ) + + solution = solve_floquet( + hamiltonian, prepared.model.period, max(96, 4 * point.steps_per_period) + ) + harmonic_cutoff = min(40, len(solution.step_propagators) // 2 - 1) + records = floquet_matrix_elements( + solution, + prepared.coupling, + harmonic_cutoff=harmonic_cutoff, + threshold=1e-14, + ) + candidates = dark_candidates(records, relative_threshold=1e-5) + strongest = sorted(records, key=lambda item: item.weight, reverse=True)[:24] + phase_states = _complex_array(result["phase_states"]) + heat = np.asarray(result["continuous"], dtype=float) + frequencies = np.asarray(result["frequency"], dtype=float) + return { + "j": point.j, + "sector": point.sector, + "model_hash": result["model_hash"], + "integrated_continuous_heat": float(trapezoid(heat, frequencies)), + "period_variance": period_variance(phase_states, prepared.coupling), + "harmonic_sum_rule_residual": harmonic_sum_rule( + solution, prepared.coupling, harmonic_cutoff + ), + "harmonic_cutoff": harmonic_cutoff, + "strongest_transitions": [asdict(item) for item in strongest], + "small_matrix_element_candidates": [ + { + "transition": asdict(item.transition), + "relative_weight": item.relative_weight, + } + for item in candidates[:24] + ], + } + + +def run_n3_heat_grid( + output: Path, + cache_directory: Path, + *, + schedule: AdaptiveSchedule | UniformAdaptiveSchedule | None = None, + exact_backend: ExactBackend = "uniform_tempo", +) -> dict[str, Any]: + cache = ConvergenceCache(cache_directory) + if exact_backend == "uniform_tempo": + active_schedule: AdaptiveSchedule | UniformAdaptiveSchedule = ( + uniform_publication_schedule() if schedule is None else schedule + ) + if not isinstance(active_schedule, UniformAdaptiveSchedule): + raise TypeError("uniform_tempo requires UniformAdaptiveSchedule") + else: + active_schedule = publication_schedule() if schedule is None else schedule + if not isinstance(active_schedule, AdaptiveSchedule): + raise TypeError("oqupy requires AdaptiveSchedule") + records: list[dict[str, Any]] = [] + odd_reference: dict[str, Any] | None = None + for sector in ("even", "odd"): + for j in (0.25, 0.5, 1.0): + if ( + exact_backend == "uniform_tempo" + and sector == "odd" + and odd_reference is not None + ): + payload = _odd_equivalent_payload(odd_reference, j) + atomic_write_result(output / f"n3_{sector}_j{j:.2f}.json", payload) + records.append(payload) + continue + if exact_backend == "uniform_tempo": + uniform_schedule = cast(UniformAdaptiveSchedule, active_schedule) + point = N3HeatPoint( + j=j, + sector=sector, + backend="uniform_tempo", + steps_per_period=uniform_schedule.steps_per_period[0], + epsrel=uniform_schedule.tolerances[0], + phase_samples=uniform_schedule.phase_samples[0], + uniform_low_rank_svd=True, + uniform_truncation="abs", + uniform_cap_rank=5_000, + uniform_max_rank=10_000, + ) + adaptive = run_uniform_adaptive( + point, + uniform_schedule, + _runner, + cache, + ) + else: + point = N3HeatPoint(j=j, sector=sector, phase_samples=3) + adaptive = run_adaptive( + point, + cast(AdaptiveSchedule, active_schedule), + _runner, + cache, + ) + payload = _adaptive_payload(adaptive) + if exact_backend == "uniform_tempo" and sector == "odd": + odd_reference = payload + atomic_write_result(output / f"n3_{sector}_j{j:.2f}.json", payload) + records.append(payload) + diagnostics = [] + for item in records: + diagnostic = _dark_diagnostic(item) + diagnostic["pt_convergence_status"] = item["adaptive_status"] + diagnostic["heat_feature_accepted"] = item["adaptive_converged"] + diagnostics.append(diagnostic) + odd = [item for item in records if item["sector"] == "odd"] + odd_difference = None + if len(odd) == 3: + curves = [np.asarray(item["continuous"], dtype=float) for item in odd] + denominator = max(float(np.max(abs(curves[0]))), 1e-15) + odd_difference = float( + max(np.max(abs(curve - curves[0])) for curve in curves[1:]) / denominator + ) + manifest = { + "method": ( + "uniform_tempo_floquet_multitime" + if exact_backend == "uniform_tempo" + else "pt_tempo_multitime" + ), + "exact_backend": exact_backend, + "converged": all(item["adaptive_converged"] for item in records), + "schedule": asdict(active_schedule), + "engine_revision": ( + records[0].get("source_commit", "unknown") + if exact_backend == "uniform_tempo" and records + else ENGINE_REVISION + ), + "phase_quadrature_evidence": ( + { + "source": "per-point adaptive evidence", + "phase_samples": list( + cast(UniformAdaptiveSchedule, active_schedule).phase_samples + ), + } + if exact_backend == "uniform_tempo" + else PHASE_QUADRATURE_EVIDENCE + ), + "points": records, + "dark_diagnostics": diagnostics, + "odd_cross_j_relative_max_difference": odd_difference, + } + atomic_write_result(output / "n3_heat_manifest.json", manifest) + return manifest + + +def run_n3_error_map( + exact_manifest: Path, + output: Path, +) -> dict[str, Any]: + """Compare every converged N=3 production point to the same-model Markov result.""" + source = json.loads(exact_manifest.read_text(encoding="utf-8")) + if not isinstance(source, dict): + raise ValueError("N=3 exact manifest must be a JSON object") + records: list[dict[str, Any]] = [] + for exact in source.get("points", []): + j = float(exact["model"]["j"]) + sector = str(exact["sector"]) + if not bool(exact.get("adaptive_converged", exact.get("converged", False))): + records.append( + { + "j": j, + "sector": sector, + "status": "resource_ceiling", + "metrics": None, + } + ) + continue + point = N3HeatPoint(**exact["final_point"]) + markov = _markov_result(point, str(exact["model_hash"])) + compatible_exact = {**exact, "converged": True} + comparison = build_error_record(compatible_exact, markov) + record = {"j": j, "sector": sector, **comparison} + records.append(record) + atomic_write_result( + output / f"n3_error_{sector}_j{j:.2f}.json", + { + "exact_fingerprint": exact.get("fingerprint"), + "exact": compatible_exact, + "markov": markov, + "comparison": record, + }, + ) + manifest = { + "method": "n3_uniform_tempo_vs_floquet_markov_qr", + "exact_backend": source.get("exact_backend"), + "model_scope": "same N=3 sector, Hamiltonian, drive and bath parameters", + "source_manifest": str(exact_manifest), + "converged": bool(records) and all( + item["status"] == "converged" for item in records + ), + "points": records, + } + atomic_write_result(output / "n3_error_map_manifest.json", manifest) + return manifest + + +def run_n4_pilot( + output: Path, + cache_directory: Path, + *, + sector: Literal["even", "odd"], + j: float = 0.25, +) -> dict[str, Any]: + """Run a convergence-gated N=4 sector point and a same-model Markov check.""" + schedule = n4_pilot_schedule() + point = N3HeatPoint( + n=4, + j=j, + sector=sector, + backend="uniform_tempo", + alpha=0.05, + steps_per_period=schedule.steps_per_period[0], + epsrel=schedule.tolerances[0], + phase_samples=schedule.phase_samples[0], + delay_periods=6 if sector == "even" else 3, + uniform_low_rank_svd=True, + uniform_truncation="abs", + uniform_cap_rank=5_000, + uniform_max_rank=10_000, + ) + adaptive = run_uniform_adaptive( + point, + schedule, + _runner, + ConvergenceCache(cache_directory), + ) + exact = _adaptive_payload(adaptive) + exact["converged"] = adaptive.converged + payload: dict[str, Any] = { + "method": "n4_uniform_tempo_convergence_pilot", + "n": 4, + "sector": sector, + "j": j, + "schedule": asdict(schedule), + "adaptive_status": adaptive.status, + "converged": adaptive.converged, + "failed_parameter": adaptive.failed_parameter, + "exact": exact, + "markov": None, + "comparison": None, + } + if adaptive.converged: + final_point = adaptive.final_point + markov = _markov_result(final_point, str(exact["model_hash"])) + payload["markov"] = markov + payload["comparison"] = build_error_record(exact, markov) + atomic_write_result(output / f"n4_{sector}_j{j:.2f}.json", payload) + return payload + + +def run_error_grid( + output: Path, + cache_directory: Path, + *, + schedule: AdaptiveSchedule | UniformAdaptiveSchedule | None = None, + exact_backend: ExactBackend = "uniform_tempo", +) -> dict[str, Any]: + cache = ConvergenceCache(cache_directory) + if exact_backend == "uniform_tempo": + active_schedule: AdaptiveSchedule | UniformAdaptiveSchedule = ( + uniform_error_schedule() if schedule is None else schedule + ) + if not isinstance(active_schedule, UniformAdaptiveSchedule): + raise TypeError("uniform_tempo requires UniformAdaptiveSchedule") + else: + active_schedule = n2_publication_schedule() if schedule is None else schedule + if not isinstance(active_schedule, AdaptiveSchedule): + raise TypeError("oqupy requires AdaptiveSchedule") + points: list[dict[str, Any]] = [] + for alpha in (0.025, 0.05, 0.1): + for ratio in (0.75, 1.0, 1.25): + if exact_backend == "uniform_tempo": + uniform_schedule = cast(UniformAdaptiveSchedule, active_schedule) + base = N2HeatPoint( + j=0.5, + backend="uniform_tempo", + alpha=alpha, + drive_ratio=ratio, + steps_per_period=uniform_schedule.steps_per_period[0], + epsrel=uniform_schedule.tolerances[0], + phase_samples=uniform_schedule.phase_samples[0], + delay_periods=n2_correlation_delay_periods(alpha), + uniform_low_rank_svd=True, + uniform_truncation="abs", + uniform_cap_rank=5_000, + uniform_max_rank=10_000, + ) + adaptive = cast( + Any, + run_uniform_adaptive( + cast(Any, base), + uniform_schedule, + cast(Any, _n2_runner), + cache, + ), + ) + else: + base = N2HeatPoint( + j=0.5, + alpha=alpha, + drive_ratio=ratio, + ) + adaptive = cast( + Any, + run_adaptive( + cast(Any, base), + cast(AdaptiveSchedule, active_schedule), + cast(Any, _n2_runner), + cache, + ), + ) + if not adaptive.converged: + points.append( + { + "alpha": alpha, + "drive_ratio": ratio, + "status": "resource_ceiling", + "metrics": None, + "failed_parameter": ( + adaptive.failed_parameter or "steady_state_gate" + ), + "adaptive_status": adaptive.status, + "evidence": [asdict(item) for item in adaptive.evidence], + } + ) + continue + exact = _adaptive_payload(adaptive) + exact["converged"] = True + markov = _markov_n2_result(adaptive.final_point, exact["model_hash"]) + record = build_error_record(exact, markov) + points.append({"alpha": alpha, "drive_ratio": ratio, **record}) + atomic_write_result( + output / f"error_a{alpha:.3f}_r{ratio:.2f}.json", + {"exact": exact, "markov": markov, "comparison": record}, + ) + manifest: dict[str, Any] = { + "method": ( + "uniform_tempo_vs_floquet_markov_qr" + if exact_backend == "uniform_tempo" + else "pt_tempo_vs_floquet_markov_qr" + ), + "exact_backend": exact_backend, + "model_scope": "N=2 interacting triplet calibration", + "points": points, + "schedule": asdict(active_schedule), + "engine_revision": ( + "per-point-source-commit" + if exact_backend == "uniform_tempo" + else N2_ENGINE_REVISION + ), + "phase_quadrature_evidence": ( + { + "source": "per-point adaptive evidence", + "phase_samples": list( + cast(UniformAdaptiveSchedule, active_schedule).phase_samples + ), + } + if exact_backend == "uniform_tempo" + else PHASE_QUADRATURE_EVIDENCE + ), + } + audit = audit_grid_manifest(manifest) + manifest["audit"] = audit + manifest["converged"] = audit["masked_points"] == 0 + atomic_write_result(output / "error_map_manifest.json", manifest) + return manifest + + +def run_model_comparison( + output: Path, + cache_directory: Path, + *, + schedule: AdaptiveSchedule | UniformAdaptiveSchedule | None = None, + exact_backend: ExactBackend = "uniform_tempo", + full_kac: bool = False, +) -> dict[str, Any]: + cache = ConvergenceCache(cache_directory) + if exact_backend == "uniform_tempo": + active_schedule: AdaptiveSchedule | UniformAdaptiveSchedule = ( + uniform_publication_schedule() if schedule is None else schedule + ) + if not isinstance(active_schedule, UniformAdaptiveSchedule): + raise TypeError("uniform_tempo requires UniformAdaptiveSchedule") + uniform_schedule = active_schedule + bath_point = N3HeatPoint( + j=0.5, + sector="even", + backend="uniform_tempo", + steps_per_period=uniform_schedule.steps_per_period[0], + epsrel=uniform_schedule.tolerances[0], + phase_samples=uniform_schedule.phase_samples[0], + uniform_low_rank_svd=True, + uniform_truncation="abs", + uniform_cap_rank=5_000, + uniform_max_rank=10_000, + ) + else: + active_schedule = publication_schedule() if schedule is None else schedule + if not isinstance(active_schedule, AdaptiveSchedule): + raise TypeError("oqupy requires AdaptiveSchedule") + bath_point = N3HeatPoint(j=0.5, sector="even", phase_samples=3) + prepared = prepare_n3_sector(bath_point) + variants = model_variants( + n=3, + j=0.5, + bath=prepared.bath, + drive_frequency=prepared.bright_gap, + ) + records: list[dict[str, Any]] = [] + for variant in variants: + point = replace( + bath_point, + normalization=variant.config.normalization, + counterterm=variant.config.counterterm, + drive_frequency=variant.config.drive_frequency, + ) + if exact_backend == "uniform_tempo": + if variant.config.normalization == "kac" and not full_kac: + adaptive = run_uniform_compression_audit( + point, + cast(UniformAdaptiveSchedule, active_schedule), + _runner, + cache, + ) + else: + adaptive = run_uniform_adaptive( + point, + cast(UniformAdaptiveSchedule, active_schedule), + _runner, + cache, + ) + payload = _adaptive_payload(adaptive) + else: + raw = _runner(point, cache) + payload = { + **raw, + "adaptive_status": "exploratory_unconverged", + "adaptive_converged": False, + "evidence": [], + "final_point": asdict(point), + "failed_parameter": "publication_convergence_resource_ceiling", + } + payload["variant"] = variant.name + payload["variant_metadata"] = variant.metadata + eta = variant.config.eta + payload["continuous_eta_rescaled"] = ( + np.asarray(payload["continuous"], dtype=float) / eta**2 + ).tolist() + atomic_write_result(output / f"model_{variant.name}.json", payload) + records.append(payload) + bounded_complete = all( + item.get("adaptive_converged") is True + for item in records + if str(item.get("variant", "")).startswith("bounded_") + ) + kac_locally_audited = all( + item.get("adaptive_converged") is True + or ( + item.get("adaptive_status") == "resource_ceiling" + and item.get("failed_parameter") == "steps_per_period" + and any( + evidence.get("parameter") == "epsrel" + and evidence.get("passed") is True + for evidence in item.get("evidence", []) + ) + ) + for item in records + if str(item.get("variant", "")).startswith("kac_") + ) + locally_complete = ( + len(records) == 4 and bounded_complete and kac_locally_audited + ) + manifest = { + "method": ( + "uniform_tempo_model_variants" + if exact_backend == "uniform_tempo" + else "pt_tempo_model_variants" + ), + "exact_backend": exact_backend, + "converged": all(item["adaptive_converged"] for item in records), + "complete": len(records) == 4, + "locally_complete": locally_complete, + "status": ( + "converged" + if records and all(item["adaptive_converged"] for item in records) + else ( + "local_resource_ceiling" + if locally_complete + else "resource_ceiling" + ) + ), + "points": records, + "schedule": asdict(active_schedule), + "engine_revision": ( + records[0].get("source_commit", "unknown") + if exact_backend == "uniform_tempo" and records + else ENGINE_REVISION + ), + "phase_quadrature_evidence": ( + { + "source": "per-point adaptive evidence", + "phase_samples": list( + cast(UniformAdaptiveSchedule, active_schedule).phase_samples + ), + } + if exact_backend == "uniform_tempo" + else PHASE_QUADRATURE_EVIDENCE + ), + "resource_policy": { + "full_kac": full_kac, + "local_default": ( + "Kac variants stop after compression convergence; use " + "--full-kac on a cluster for timestep and phase refinement" + ), + }, + } + atomic_write_result(output / "model_comparison_manifest.json", manifest) + return manifest + + +def audit_paper_results(directory: Path) -> tuple[bool, list[str]]: + """Audit the three publication manifests against the declared final gates.""" + + def evidence_failures( + records: Any, + label: str, + ) -> list[str]: + local: list[str] = [] + if not isinstance(records, list): + return [f"{label} has no convergence evidence"] + passed = [item for item in records if item.get("passed") is True] + parameters = {item.get("parameter") for item in passed} + required = {"epsrel", "steps_per_period", "phase_samples"} + if not required.issubset(parameters): + local.append( + f"{label} lacks passed compression/timestep/phase evidence" + ) + return local + timestep_records = [ + item for item in passed if item.get("parameter") == "steps_per_period" + ] + compression_steps = { + int(item["refined_steps_per_period"]) + for item in passed + if item.get("parameter") == "epsrel" + and item.get("refined_steps_per_period") is not None + } + for item in timestep_records: + compared = { + int(item["coarse_steps_per_period"]), + int(item["refined_steps_per_period"]), + } + if not compared.issubset(compression_steps): + local.append( + f"{label} did not converge compression on both timestep grids" + ) + break + return local + + def point_failures(point: dict[str, Any], label: str) -> list[str]: + local: list[str] = [] + if point.get("adaptive_status") != "converged": + local.append(f"{label} status is not converged") + if point.get("adaptive_converged") is not True: + local.append(f"{label} adaptive_converged is not true") + diagnostics = point.get("diagnostics", {}) + gates = { + "fixed_point_residual": (1e-3, "maximum"), + "trace_error": (5e-3, "maximum"), + "hermiticity_error": (5e-3, "maximum"), + "connected_tail_amplitude": (5e-2, "maximum"), + "minimum_density_eigenvalue": (-5e-3, "minimum"), + } + for name, (threshold, direction) in gates.items(): + try: + value = float(diagnostics[name]) + except (KeyError, TypeError, ValueError): + local.append(f"{label} lacks finite diagnostic {name}") + continue + if not np.isfinite(value): + local.append(f"{label} diagnostic {name} is non-finite") + elif direction == "maximum" and value > threshold: + local.append(f"{label} diagnostic {name} exceeds {threshold:g}") + elif direction == "minimum" and value < threshold: + local.append(f"{label} diagnostic {name} is below {threshold:g}") + local.extend(evidence_failures(point.get("evidence"), label)) + return local + + failures: list[str] = [] + required = ( + "n3_heat_manifest.json", + "error_map_manifest.json", + "model_comparison_manifest.json", + ) + import json + + loaded: dict[str, dict[str, Any]] = {} + for name in required: + path = directory / name + if not path.is_file(): + failures.append(f"missing {name}") + continue + loaded[name] = json.loads(path.read_text(encoding="utf-8")) + if "n3_heat_manifest.json" in loaded: + value = loaded["n3_heat_manifest.json"] + points = value.get("points", []) + if value.get("exact_backend") != "uniform_tempo": + failures.append("N=3 manifest does not use uniform_tempo") + if value.get("converged") is not True: + failures.append("N=3 manifest is not converged") + if len(points) != 6: + failures.append("N=3 manifest must contain six points") + observed = { + (point.get("sector"), float(point.get("model", {}).get("j", np.nan))) + for point in points + } + expected = { + (sector, j) + for sector in ("even", "odd") + for j in (0.25, 0.5, 1.0) + } + if observed != expected: + failures.append("N=3 manifest sector/J grid is incomplete") + for point in points: + label = ( + f"N=3 {point.get('sector')} " + f"J={point.get('model', {}).get('j')}" + ) + failures.extend(point_failures(point, label)) + if value.get("odd_cross_j_relative_max_difference") is None: + failures.append("N=3 odd-sector invariance diagnostic is missing") + elif float(value["odd_cross_j_relative_max_difference"]) > 1e-12: + failures.append("N=3 odd-sector J-invariance check failed") + if "error_map_manifest.json" in loaded: + try: + value = loaded["error_map_manifest.json"] + audit = audit_grid_manifest(value) + if value.get("exact_backend") != "uniform_tempo": + failures.append("error map does not use uniform_tempo") + if audit["masked_points"] != 0 or value.get("converged") is not True: + failures.append("error map contains unconverged or masked points") + for point in value.get("points", []): + label = ( + f"error grid alpha={point.get('alpha')} " + f"ratio={point.get('drive_ratio')}" + ) + metrics = point.get("metrics", {}) + if set(metrics) != {"trace_distance", "correlation", "heat"}: + failures.append(f"{label} lacks the three error metrics") + elif not all(np.isfinite(float(item)) for item in metrics.values()): + failures.append(f"{label} has a non-finite error metric") + failures.extend( + evidence_failures(point.get("convergence_evidence"), label) + ) + except ValueError as exc: + failures.append(str(exc)) + if "model_comparison_manifest.json" in loaded: + value = loaded["model_comparison_manifest.json"] + points = value.get("points", []) + if value.get("exact_backend") != "uniform_tempo": + failures.append("model comparison does not use uniform_tempo") + if len(points) != 4: + failures.append("model manifest must contain four variants") + if not value.get("complete"): + failures.append("model comparison is incomplete") + if value.get("locally_complete") is not True: + failures.append("model comparison has not reached its declared local endpoint") + if len({point.get("variant") for point in points}) != 4: + failures.append("model comparison variants are not distinct") + for point in points: + label = f"model variant {point.get('variant')}" + if point.get("adaptive_converged") is True: + failures.extend(point_failures(point, label)) + continue + if not str(point.get("variant", "")).startswith("kac_"): + failures.append(f"{label} is not converged") + continue + if ( + point.get("adaptive_status") != "resource_ceiling" + or point.get("failed_parameter") != "steps_per_period" + ): + failures.append(f"{label} lacks an audited timestep resource ceiling") + compression = [ + item + for item in point.get("evidence", []) + if item.get("parameter") == "epsrel" and item.get("passed") is True + ] + if not compression: + failures.append(f"{label} lacks passed compression evidence") + return not failures, failures diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/plotting.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/plotting.py new file mode 100644 index 000000000..67abdd80f --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/plotting.py @@ -0,0 +1,632 @@ +"""Publication-oriented deterministic baseline plots.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.figure import Figure +from matplotlib.patches import Circle, Rectangle + +from .heat_valve_audit import audit_heat_valve_manifest + + +def _save(figure: Figure, stem: Path) -> None: + stem.parent.mkdir(parents=True, exist_ok=True) + figure.savefig( + stem.with_suffix(".pdf"), + bbox_inches="tight", + metadata={"Creator": "floquet-if"}, + ) + figure.savefig(stem.with_suffix(".png"), dpi=180, bbox_inches="tight") + plt.close(figure) + + +def plot_n2(result: dict[str, Any], stem: Path) -> None: + rows = result["data"] + j = np.array([row["j"] for row in rows]) + figure, axes = plt.subplots(1, 2, figsize=(9, 3.6)) + axes[0].plot(j, [row["gap_low"] for row in rows], label=r"$\Delta_{\rm low}$") + axes[0].plot(j, [row["gap_high"] for row in rows], label=r"$\Delta_{\rm high}$") + axes[0].set(xlabel=r"$J/\Omega$", ylabel=r"gap$/\Omega$") + axes[0].legend(frameon=False) + axes[1].plot(j, [row["weight_low"] for row in rows], label="low") + axes[1].plot(j, [row["weight_high"] for row in rows], label="high") + axes[1].set(xlabel=r"$J/\Omega$", ylabel=r"$|\langle f|S_2|i\rangle|^2$") + axes[1].legend(frameon=False) + figure.suptitle("N=2 interacting triplet — exact diagonalization") + figure.tight_layout() + _save(figure, stem) + + +def plot_n3(result: dict[str, Any], stem: Path) -> None: + rows = result["data"] + j = np.array([row["j"] for row in rows]) + gaps = np.array([row["primary_even_gap"] for row in rows]) + weights = np.array([row["primary_even_weight"] for row in rows]) + figure, axes = plt.subplots(1, 2, figsize=(9, 3.6)) + axes[0].loglog(j[j > 0], gaps[j > 0], "o-", label="exact primary gap") + asymptotic = 1 / (4 * j[j > 0] ** 2) + axes[0].loglog(j[j > 0], asymptotic, "--", label=r"$\Omega^3/(4J^2)$") + axes[0].set(xlabel=r"$J/\Omega$", ylabel=r"gap$/\Omega$") + axes[0].legend(frameon=False) + axes[1].plot(j, weights, "o-", label="reflection-even") + axes[1].set(xlabel=r"$J/\Omega$", ylabel="primary bright weight") + axes[1].legend(frameon=False) + figure.suptitle("N=3 symmetry-resolved collective mode") + figure.tight_layout() + _save(figure, stem) + + +def plot_backend_comparison(result: dict[str, Any], stem: Path) -> None: + rows = result["data"] + alpha = [row["alpha"] for row in rows] + distance = [row["trace_distance"] for row in rows] + figure, axis = plt.subplots(figsize=(5.2, 3.7)) + axis.plot(alpha, distance, "o-", color="#D55E00") + axis.set( + xlabel=r"bath coupling $\alpha$", + ylabel="trace distance", + title="Finite-memory IF vs Floquet-Markov\n(both approximate)", + ) + axis.grid(alpha=0.25) + figure.tight_layout() + _save(figure, stem) + + +def plot_heat_spectrum(result: dict[str, Any], stem: Path) -> None: + rows = result["data"] + frequencies = np.array([row["frequency"] for row in rows]) + current = np.array([row["continuous"] for row in rows]) + figure, axis = plt.subplots(figsize=(6.2, 3.8)) + axis.plot(frequencies, current, color="#0072B2", label="continuous") + delta_axis = axis.twinx() + peak_frequencies = [peak["frequency"] for peak in result["delta_peaks"]] + peak_weights = [peak["weight"] for peak in result["delta_peaks"]] + delta_axis.semilogy( + peak_frequencies, + peak_weights, + "D", + color="#D55E00", + label=r"coherent $\delta$ weight", + ) + delta_axis.set_ylabel(r"analytic $\delta$ weight", color="#D55E00") + delta_axis.tick_params(axis="y", colors="#D55E00") + method_title = ( + "PT-TEMPO multitime" + if result["method"] == "pt_tempo_multitime" + else "Floquet-Markov/QRT" + ) + axis.set( + xlabel=r"bath frequency $\omega/\Omega$", + ylabel=r"$\bar j(\omega)$", + title=f"N=2 heat-current spectrum — {method_title}", + ) + handles, labels = axis.get_legend_handles_labels() + delta_handles, delta_labels = delta_axis.get_legend_handles_labels() + axis.legend(handles + delta_handles, labels + delta_labels, frameon=False) + axis.grid(alpha=0.2) + figure.tight_layout() + _save(figure, stem) + + +def plot_n3_pt_dynamics(result: dict[str, Any], stem: Path) -> None: + rows = result["data"] + phase = np.array([row["phase_index"] for row in rows]) / len(rows) + magnetization = np.array([row["magnetization"] for row in rows]) + figure, axis = plt.subplots(figsize=(5.5, 3.6)) + axis.plot(phase, magnetization, "o-", color="#009E73") + axis.set( + xlabel=r"drive phase $t/T$", + ylabel=r"$\langle S_3\rangle$", + title="N=3 reflection-even periodic state — PT-TEMPO", + ) + axis.grid(alpha=0.2) + figure.tight_layout() + _save(figure, stem) + + +def plot_n3_sector_heat(manifest: dict[str, Any], stem: Path) -> None: + backend_label = ( + "uniform TEMPO" + if manifest.get("exact_backend") == "uniform_tempo" + else "PT-TEMPO" + ) + figure, axes = plt.subplots(1, 2, figsize=(10, 3.8), sharey=True) + for axis, sector in zip(axes, ("even", "odd"), strict=True): + for point in manifest["points"]: + if point["sector"] != sector: + continue + axis.plot( + point["frequency"], + point["continuous"], + "--" if not point["adaptive_converged"] else "-", + label=( + rf"$J/\Omega={point['model']['j']:g}$ " + f"({point.get('adaptive_status', 'converged')})" + ), + ) + axis.set( + xlabel=r"bath frequency $\omega/\Omega$", + title=f"reflection-{sector} — {backend_label}", + ) + axis.grid(alpha=0.2) + if axis.lines: + axis.legend(frameon=False, fontsize=8) + axes[0].set_ylabel(r"continuous $\bar j(\omega)$") + qualifier = ( + "converged grid" + if manifest.get("converged") + else "dashed curves are resource-limited" + ) + figure.suptitle(rf"$N=3$ symmetry-resolved calorimetry — {qualifier}") + figure.tight_layout() + _save(figure, stem) + + +def plot_odd_sector_difference(manifest: dict[str, Any], stem: Path) -> None: + odd = [ + point + for point in manifest["points"] + if point["sector"] == "odd" + ] + figure, axis = plt.subplots(figsize=(6.1, 3.7)) + if odd: + reference = np.asarray(odd[0]["continuous"], dtype=float) + frequency = np.asarray(odd[0]["frequency"], dtype=float) + for point in odd[1:]: + difference = np.asarray(point["continuous"], dtype=float) - reference + axis.plot( + frequency, + difference, + label=rf"$J={point['model']['j']:g}$ minus $J={odd[0]['model']['j']:g}$", + ) + axis.axhline(0, color="black", lw=0.7) + axis.set( + xlabel=r"bath frequency $\omega/\Omega$", + ylabel=r"$\Delta\bar j(\omega)$", + title=r"Reflection-odd heat spectrum: exact $J$-invariance check", + ) + axis.grid(alpha=0.2) + if axis.lines: + axis.legend(frameon=False) + figure.tight_layout() + _save(figure, stem) + + +def plot_error_maps(manifest: dict[str, Any], stem: Path) -> None: + alphas = [0.025, 0.05, 0.1] + ratios = [0.75, 1.0, 1.25] + names = ( + ("trace_distance", r"$D_\rho$"), + ("correlation", r"$\epsilon_C$"), + ("heat", r"$\epsilon_j$"), + ) + figure, axes = plt.subplots(1, 3, figsize=(11.5, 3.5)) + for axis, (name, label) in zip(axes, names, strict=True): + values = np.full((len(alphas), len(ratios)), np.nan) + for point in manifest["points"]: + if point["status"] != "converged": + continue + row = alphas.index(float(point["alpha"])) + column = ratios.index(float(point["drive_ratio"])) + values[row, column] = point["metrics"][name] + image = axis.imshow(values, origin="lower", aspect="auto", cmap="magma") + axis.set_xticks(range(len(ratios)), labels=ratios) + axis.set_yticks(range(len(alphas)), labels=alphas) + axis.set( + xlabel=r"$\omega_d/\Delta_g$", + ylabel=r"$\alpha$", + title=label, + ) + for row in range(len(alphas)): + for column in range(len(ratios)): + if np.isnan(values[row, column]): + text = "masked" + elif name == "trace_distance": + text = f"{values[row, column]:.4f}" + else: + text = f"{values[row, column]:.2g}" + if np.isnan(values[row, column]): + axis.add_patch( + Rectangle( + (column - 0.5, row - 0.5), + 1, + 1, + fill=False, + hatch="///", + edgecolor="#777777", + linewidth=0.0, + ) + ) + axis.text( + column, + row, + text, + ha="center", + va="center", + color="black" if np.isnan(values[row, column]) else "white", + fontsize=8, + ) + figure.colorbar(image, ax=axis, fraction=0.046) + scope = manifest.get("model_scope", "audited calibration grid") + exact_label = ( + "uniform TEMPO" + if manifest.get("exact_backend") == "uniform_tempo" + else "PT-TEMPO" + ) + figure.suptitle(f"{exact_label} vs Floquet-Markov/QRT — {scope}") + figure.tight_layout() + _save(figure, stem) + + +def plot_n3_error_maps(manifest: dict[str, Any], stem: Path) -> None: + """Plot same-parameter N=3 exact-versus-Markov errors by sector and J.""" + sectors = ("even", "odd") + couplings = (0.25, 0.5, 1.0) + names = ( + ("trace_distance", r"$D_\rho$"), + ("correlation", r"$\epsilon_C$"), + ("heat", r"$\epsilon_j$"), + ) + figure, axes = plt.subplots(1, 3, figsize=(11.5, 3.5)) + for axis, (name, label) in zip(axes, names, strict=True): + values = np.full((len(sectors), len(couplings)), np.nan) + for point in manifest["points"]: + if point["status"] != "converged": + continue + row = sectors.index(str(point["sector"])) + column = couplings.index(float(point["j"])) + values[row, column] = point["metrics"][name] + image = axis.imshow(values, origin="upper", aspect="auto", cmap="magma") + axis.set_xticks(range(len(couplings)), labels=couplings) + axis.set_yticks(range(len(sectors)), labels=sectors) + axis.set(xlabel=r"$J/\Omega$", ylabel="reflection sector", title=label) + for row in range(len(sectors)): + for column in range(len(couplings)): + value = values[row, column] + axis.text( + column, + row, + "masked" if np.isnan(value) else f"{value:.2g}", + ha="center", + va="center", + color="white", + fontsize=8, + ) + figure.colorbar(image, ax=axis, fraction=0.046, pad=0.04) + figure.suptitle("N=3 same-model UniformTEMPO versus Floquet-Markov/QRT") + figure.tight_layout() + _save(figure, stem) + + +def plot_n4_pilot_comparison(result: dict[str, Any], stem: Path) -> None: + """Plot one convergence-gated N=4 spectrum against its same-model benchmark.""" + if not result.get("converged") or result.get("markov") is None: + raise ValueError("N=4 pilot must converge before plotting a comparison") + exact = result["exact"] + markov = result["markov"] + metrics = result["comparison"]["metrics"] + figure, axis = plt.subplots(figsize=(6.5, 3.9)) + axis.plot( + exact["frequency"], + exact["continuous"], + label="UniformTEMPO", + color="#0072B2", + ) + axis.plot( + markov["frequency"], + markov["continuous"], + "--", + label="Floquet-Markov/QRT", + color="#D55E00", + ) + for peak in exact["delta_peaks"]: + frequency = float(peak["frequency"]) + if frequency <= 3: + axis.axvline(frequency, color="black", alpha=0.25, lw=0.7) + axis.set( + xlim=(0, 3), + xlabel=r"bath frequency $\omega/\Omega$", + ylabel=r"continuous $\bar j(\omega)$", + title=( + rf"$N=4$ reflection-{result['sector']}, $J/\Omega={result['j']:g}$" + "\n" + rf"same-model heat error $\epsilon_j={metrics['heat']:.3g}$" + ), + ) + axis.grid(alpha=0.2) + axis.legend(frameon=False) + figure.tight_layout() + _save(figure, stem) + + +def plot_dark_diagnostics(manifest: dict[str, Any], stem: Path) -> None: + diagnostics = sorted( + [ + item + for item in manifest.get("dark_diagnostics", []) + if item["sector"] == "even" + ], + key=lambda item: item["j"], + ) + figure, axes = plt.subplots(1, 2, figsize=(9.5, 3.7)) + j_values = [item["j"] for item in diagnostics] + strongest = [ + max( + ( + record["weight"] + for record in item["strongest_transitions"] + if record["source"] != record["target"] + ), + default=np.nan, + ) + for item in diagnostics + ] + axes[0].semilogy(j_values, strongest, "o-", color="#0072B2") + axes[0].set( + xlabel=r"$J/\Omega$", + ylabel=r"largest off-diagonal $|S_{\alpha\beta}^{(m)}|^2$", + title="Floquet matrix-element diagnostic", + ) + axes[1].plot( + j_values, + [item["integrated_continuous_heat"] for item in diagnostics], + "s-", + color="#D55E00", + label="integrated heat", + ) + axes[1].plot( + j_values, + [item["period_variance"] for item in diagnostics], + "o--", + color="#009E73", + label=r"$\overline{\mathrm{Var}(S)}$", + ) + axes[1].set(xlabel=r"$J/\Omega$", title="Heat and collective variance") + axes[1].legend(frameon=False) + for axis in axes: + axis.grid(alpha=0.2) + qualifier = "converged heat" if manifest.get("converged") else "provisional heat" + figure.suptitle(f"Floquet diagnostics with {qualifier}", fontsize=12) + figure.tight_layout() + _save(figure, stem) + + +def plot_model_variants(manifest: dict[str, Any], stem: Path) -> None: + figure, axes = plt.subplots(1, 2, figsize=(10, 3.8)) + for point in manifest["points"]: + style = "-" if point.get("adaptive_converged") else "--" + axes[0].plot( + point["frequency"], point["continuous"], style, label=point["variant"] + ) + axes[1].plot( + point["frequency"], + point["continuous_eta_rescaled"], + style, + label=point["variant"], + ) + axes[0].set(title="Raw heat spectra", ylabel=r"$\bar j(\omega)$") + axes[1].set(title=r"Diagnostic $\bar j(\omega)/\eta^2$") + for axis in axes: + axis.set_xlabel(r"bath frequency $\omega/\Omega$") + axis.grid(alpha=0.2) + axis.legend(frameon=False, fontsize=8) + backend_label = ( + "uniform TEMPO" + if manifest.get("exact_backend") == "uniform_tempo" + else "PT-TEMPO" + ) + if manifest.get("converged"): + qualifier = "fully converged" + elif manifest.get("locally_complete"): + qualifier = "bounded converged; Kac compression-audited" + else: + qualifier = "exploratory" + figure.suptitle( + f"N=3 normalization/counterterm comparison — {qualifier} {backend_label}" + ) + figure.tight_layout() + _save(figure, stem) + + +def plot_heat_valve_hero(manifest: dict[str, Any], stem: Path) -> None: + """Render the audited four-panel Floquet heat-valve summary.""" + audit = audit_heat_valve_manifest(manifest) + points = list(manifest.get("points", ())) + figure, axes = plt.subplots(2, 2, figsize=(11.2, 8.0)) + colors = {1: "#0072B2", 2: "#E69F00", 3: "#009E73"} + + for n in (1, 2, 3): + rows = sorted( + [item for item in points if int(item["point"]["n"]) == n], + key=lambda item: float(item["point"]["xi"]), + ) + if not rows: + continue + xi = np.asarray([item["point"]["xi"] for item in rows], dtype=float) + heat = np.asarray( + [item["integrated_absolute_heat"] for item in rows], + dtype=float, + ) + residue = np.asarray( + [item["visible_residue_weight"] for item in rows], + dtype=float, + ) + heat_scale = float(np.max(heat)) + 1e-15 + residue_scale = float(np.max(residue)) + 1e-15 + style = "-" if all(item.get("converged", False) for item in rows) else ":" + axes[0, 0].plot( + xi, + heat / heat_scale, + marker="o", + linestyle=style, + color=colors[n], + label=rf"$N={n}$ heat", + ) + axes[0, 0].plot( + xi, + residue / residue_scale, + marker="s", + linestyle="--", + color=colors[n], + alpha=0.75, + label=rf"$N={n}$ residue", + ) + axes[0, 0].set( + xlabel=r"$\xi=2A/\omega_d$", + ylabel="within-size normalized weight", + title="Heat and observable transfer-mode weight", + ) + axes[0, 0].grid(alpha=0.2) + axes[0, 0].legend(frameon=False, fontsize=7, ncol=2) + + target: dict[str, Any] | None = None + selected_n3 = [ + item + for item in manifest.get("selected_points", ()) + if int(item["n"]) == 3 + ] + if len(selected_n3) == 3: + target_xi = float(selected_n3[1]["xi"]) + target = next( + ( + item + for item in points + if int(item["point"]["n"]) == 3 + and np.isclose(float(item["point"]["xi"]), target_xi) + ), + None, + ) + if target is None and points: + target = points[0] + + unit_axis = axes[0, 1] + unit_axis.add_patch( + Circle((0, 0), 1, fill=False, color="#777777", linestyle="--") + ) + if target is not None: + poles = list(target.get("poles", ())) + real = np.asarray( + [item["eigenvalue"].get("real", 0.0) for item in poles], + dtype=float, + ) + imag = np.asarray( + [item["eigenvalue"].get("imag", 0.0) for item in poles], + dtype=float, + ) + log_residue = np.log10( + np.asarray( + [item["residue"]["abs"] for item in poles], + dtype=float, + ) + + 1e-15 + ) + if len(poles): + scatter = unit_axis.scatter( + real, + imag, + c=log_residue, + cmap="viridis", + edgecolor="black", + linewidth=0.3, + ) + figure.colorbar( + scatter, + ax=unit_axis, + label=r"$\log_{10}|A_a|$", + fraction=0.046, + ) + unit_axis.set( + xlabel=r"$\mathrm{Re}\,\lambda_a$", + ylabel=r"$\mathrm{Im}\,\lambda_a$", + title=r"Floquet transfer poles ($N=3$ selected minimum)", + xlim=(-1.08, 1.08), + ylim=(-1.08, 1.08), + aspect="equal", + ) + unit_axis.axhline(0, color="#BBBBBB", lw=0.5) + unit_axis.axvline(0, color="#BBBBBB", lw=0.5) + + spectrum_axis = axes[1, 0] + n3_rows = sorted( + [item for item in points if int(item["point"]["n"]) == 3], + key=lambda item: float(item["point"]["xi"]), + ) + for index, item in enumerate(n3_rows): + role = ("lower flank", "minimum", "upper flank")[min(index, 2)] + spectrum_axis.plot( + item.get("frequency", ()), + item.get("continuous", ()), + "-" if item.get("converged", False) else ":", + label=rf"{role}, $\xi={float(item['point']['xi']):.2f}$", + ) + if target is not None: + for item in target.get("poles", ())[:4]: + center = abs(float(item.get("quasifrequency", 0.0))) + width = max(float(item.get("decay_rate", 0.0)), 0.0) + spectrum_axis.axvline(center, color="#555555", lw=0.7, alpha=0.5) + spectrum_axis.axvspan( + max(0.0, center - width), + center + width, + color="#999999", + alpha=0.08, + ) + spectrum_axis.set( + xlabel=r"bath frequency $\omega/\Omega$", + ylabel=r"continuous $\bar j(\omega)$", + title="Exact heat spectrum with pole centres/widths", + ) + spectrum_axis.grid(alpha=0.2) + if spectrum_axis.lines: + spectrum_axis.legend(frameon=False, fontsize=7) + + contrast_axis = axes[1, 1] + sizes = np.asarray([1, 2, 3]) + heat_contrast = np.asarray( + [audit.metrics.get(f"heat_contrast_n{n}", np.nan) for n in sizes] + ) + residue_contrast = np.asarray( + [ + 1 / audit.metrics.get(f"residue_ratio_n{n}", np.nan) + for n in sizes + ] + ) + contrast_axis.bar( + sizes - 0.16, + heat_contrast, + width=0.32, + color="#0072B2", + label="heat contrast", + ) + contrast_axis.bar( + sizes + 0.16, + residue_contrast, + width=0.32, + color="#CC79A7", + label="residue contrast", + ) + contrast_axis.axhline(10, color="#D55E00", linestyle="--", label="dark gate") + contrast_axis.set( + xlabel="system size $N$", + ylabel="weaker-flank / minimum", + title="Size dependence and tenfold claim gate", + xticks=sizes, + yscale="log", + ) + contrast_axis.grid(axis="y", alpha=0.2) + contrast_axis.legend(frameon=False, fontsize=8) + + title = ( + "audited dark channel" + if audit.dark_channel_passed + else "candidate; claim gates not met" + ) + figure.suptitle(f"Pole-resolved Floquet heat valve — {title}") + figure.tight_layout() + _save(figure, stem) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/poles.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/poles.py new file mode 100644 index 000000000..1b308003c --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/poles.py @@ -0,0 +1,189 @@ +"""Observable residues and tracking for Floquet transfer poles.""" + +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np +from numpy.typing import NDArray +from scipy.optimize import linear_sum_assignment + + +@dataclass(frozen=True) +class TransferPole: + eigenvalue: complex + decay_rate: float + quasifrequency: float + eigenpair_residual: float + + +@dataclass(frozen=True) +class PoleResidue: + pole: TransferPole + residue: complex + + +@dataclass(frozen=True) +class PoleFit: + residues: tuple[PoleResidue, ...] + reconstruction: NDArray[np.complex128] + stroboscopic_delays: NDArray[np.float64] + reconstruction_residual: float + condition_number: float + + +@dataclass(frozen=True) +class PoleMatch: + previous_index: int + current_index: int + distance: float + ambiguous: bool + + +def transfer_poles( + eigenvalues: NDArray[np.complex128], + eigenpair_residuals: NDArray[np.float64], + period: float, + *, + steady_tolerance: float = 5e-3, +) -> tuple[TransferPole, ...]: + """Remove the resolved steady pole and convert the rest to rates.""" + values = np.asarray(eigenvalues, dtype=np.complex128) + residuals = np.asarray(eigenpair_residuals, dtype=float) + if values.ndim != 1 or residuals.ndim != 1 or values.shape != residuals.shape: + raise ValueError("transfer eigenvalues and residuals must be equal vectors") + if values.size < 2: + raise ValueError("at least one steady and one decaying pole are required") + if period <= 0 or steady_tolerance <= 0: + raise ValueError("period and steady_tolerance must be positive") + if not np.all(np.isfinite(values)) or not np.all(np.isfinite(residuals)): + raise ValueError("transfer pole data must be finite") + if np.any(residuals < 0): + raise ValueError("eigenpair residuals must be nonnegative") + + steady = int(np.argmin(abs(values - 1))) + if abs(values[steady] - 1) > steady_tolerance: + raise ValueError("transfer spectrum has no resolved steady pole") + + records: list[TransferPole] = [] + for index, (value, residual) in enumerate( + zip(values, residuals, strict=True) + ): + if index == steady: + continue + magnitude = float(abs(value)) + if magnitude <= np.finfo(float).tiny: + raise ValueError("zero transfer eigenvalues cannot define finite rates") + records.append( + TransferPole( + eigenvalue=complex(value), + decay_rate=float(-np.log(magnitude) / period), + quasifrequency=float(np.angle(value) / period), + eigenpair_residual=float(residual), + ) + ) + return tuple( + sorted(records, key=lambda item: abs(item.eigenvalue), reverse=True) + ) + + +def fit_pole_residues( + poles: tuple[TransferPole, ...], + delays: NDArray[np.float64], + connected: NDArray[np.complex128], + period: float, + max_modes: int, +) -> PoleFit: + """Fit transfer-pole residues to stroboscopic connected correlations.""" + delay_values = np.asarray(delays, dtype=float) + data_values = np.asarray(connected, dtype=np.complex128) + if ( + delay_values.ndim != 1 + or data_values.ndim != 1 + or delay_values.shape != data_values.shape + ): + raise ValueError("delays and connected correlation must be equal vectors") + if period <= 0: + raise ValueError("period must be positive") + if isinstance(max_modes, bool) or max_modes < 1 or max_modes > len(poles): + raise ValueError("max_modes must select one or more available poles") + if not np.all(np.isfinite(delay_values)) or not np.all(np.isfinite(data_values)): + raise ValueError("correlation fit data must be finite") + if np.any(delay_values < 0): + raise ValueError("delays must be nonnegative") + + scaled = delay_values / period + orders = np.rint(scaled).astype(int) + mask = np.isclose(scaled, orders, rtol=0.0, atol=1e-8) + stroboscopic_delays = delay_values[mask] + stroboscopic_orders = orders[mask] + stroboscopic_data = data_values[mask] + if len(stroboscopic_data) <= max_modes: + raise ValueError( + "the pole fit requires more stroboscopic samples than modes" + ) + + selected = poles[:max_modes] + design = np.column_stack( + [item.eigenvalue**stroboscopic_orders for item in selected] + ).astype(np.complex128, copy=False) + coefficients, _, _, _ = np.linalg.lstsq( + design, + stroboscopic_data, + rcond=None, + ) + reconstruction = np.asarray(design @ coefficients, dtype=np.complex128) + denominator = float(np.sum(abs(stroboscopic_data))) + 1e-15 + reconstruction_residual = float( + np.sum(abs(reconstruction - stroboscopic_data)) / denominator + ) + return PoleFit( + residues=tuple( + PoleResidue(pole=pole, residue=complex(residue)) + for pole, residue in zip(selected, coefficients, strict=True) + ), + reconstruction=reconstruction, + stroboscopic_delays=np.asarray(stroboscopic_delays, dtype=float), + reconstruction_residual=reconstruction_residual, + condition_number=float(np.linalg.cond(design)), + ) + + +def match_transfer_poles( + previous: tuple[TransferPole, ...], + current: tuple[TransferPole, ...], + *, + ambiguity_tolerance: float = 1e-6, +) -> tuple[PoleMatch, ...]: + """Match two transfer spectra by minimum total complex-plane distance.""" + if ambiguity_tolerance < 0: + raise ValueError("ambiguity_tolerance must be nonnegative") + if not previous or not current: + return () + cost = np.asarray( + [ + [ + abs(previous_pole.eigenvalue - current_pole.eigenvalue) + for current_pole in current + ] + for previous_pole in previous + ], + dtype=float, + ) + rows, columns = linear_sum_assignment(cost) + records: list[PoleMatch] = [] + for row, column in zip(rows, columns, strict=True): + ordered = np.sort(cost[row]) + ambiguous = bool( + len(ordered) > 1 + and ordered[1] - ordered[0] <= ambiguity_tolerance + ) + records.append( + PoleMatch( + previous_index=int(row), + current_index=int(column), + distance=float(cost[row, column]), + ambiguous=ambiguous, + ) + ) + return tuple(sorted(records, key=lambda item: item.previous_index)) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/pt_experiments.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/pt_experiments.py new file mode 100644 index 000000000..e0056d51b --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/pt_experiments.py @@ -0,0 +1,192 @@ +"""Long-running PT-TEMPO baselines kept separate from fast CI experiments.""" + +from __future__ import annotations + +from dataclasses import asdict + +import numpy as np + +from .backends.pt_tempo import PtTempoBackend +from .config import BathConfig, ModelConfig +from .heat_current import heat_current_spectrum +from .models import coupling_operator, ising_hamiltonian +from .spectra import diagonalize +from .symmetry import Sector, n2_sectors, n3_reflection_sectors, project + + +def _projected_model(config: ModelConfig, sector: Sector) -> tuple[np.ndarray, np.ndarray]: + return ( + project(ising_hamiltonian(config), sector), + project(coupling_operator(config), sector), + ) + + +def n2_pt_tempo_heat() -> dict[str, object]: + """Non-Markovian N=2 heat spectrum with an explicit memory comparison.""" + j = 0.5 + gap = float(np.sqrt(1 + j**2) - j) + model = ModelConfig( + n=2, + j=j, + drive_amplitude=0.2, + drive_frequency=gap, + ) + bath = BathConfig(alpha=0.1, cutoff=2.5) + _, triplet = n2_sectors() + h0, coupling = _projected_model(model, triplet) + + def hamiltonian(time: float) -> np.ndarray: + return np.asarray( + h0 + + model.drive_amplitude + * np.cos(model.drive_frequency * time) + * coupling, + dtype=np.complex128, + ) + + ground = diagonalize(h0).states[:, 0] + initial = np.outer(ground, ground.conj()) + period_steps = 16 + steady_periods = 10 + delay_periods = 4 + total_periods = steady_periods + 1 + delay_periods + dt = model.period / period_steps + backend = PtTempoBackend() + reference = backend.run( + hamiltonian, + coupling, + initial, + bath, + dt, + total_periods * period_steps, + memory_steps=5, + epsrel=1e-5, + ) + shorter_memory = backend.run( + hamiltonian, + coupling, + initial, + bath, + dt, + steady_periods * period_steps, + memory_steps=4, + epsrel=1e-5, + ) + phase_start = steady_periods * period_steps + phase_residual = float( + np.linalg.norm( + reference.result.density_matrices[phase_start] + - reference.result.density_matrices[phase_start - period_steps] + ) + ) + memory_residual = float( + np.linalg.norm( + reference.result.density_matrices[phase_start] + - shorter_memory.result.density_matrices[-1] + ) + ) + correlation = backend.period_averaged_correlation( + reference, + coupling, + phase_start, + period_steps, + delay_periods * period_steps, + model.drive_frequency, + ) + frequencies = np.linspace(0, 3, 401) + heat = heat_current_spectrum(correlation, bath, frequencies) + tail = float(abs(correlation.connected[-1])) + converged = ( + reference.result.converged + and phase_residual < 1e-3 + and memory_residual < 5e-2 + and tail < 5e-2 + ) + return { + "method": "pt_tempo_multitime", + "converged": converged, + "diagnostics": { + **reference.result.diagnostics, + "phase_residual": phase_residual, + "memory_k4_to_k5_residual": memory_residual, + "connected_tail_amplitude": tail, + "tau_max": float(correlation.delays[-1]), + "dt": dt, + "memory_steps": 5, + "epsrel": 1e-5, + }, + "model": asdict(model), + "bath": asdict(bath), + "delta_peaks": [asdict(peak) for peak in heat.delta_peaks], + "data": [ + {"frequency": float(frequency), "continuous": float(current)} + for frequency, current in zip( + heat.frequencies, heat.continuous, strict=True + ) + ], + } + + +def n3_pt_tempo_dynamics() -> dict[str, object]: + """N=3 reflection-even non-Markovian periodic-state convergence baseline.""" + model = ModelConfig( + n=3, + j=0.5, + drive_amplitude=0.2, + drive_frequency=0.4450418679126287, + ) + bath = BathConfig(alpha=0.1, cutoff=2.5) + _, even = n3_reflection_sectors() + h0, coupling = _projected_model(model, even) + + def hamiltonian(time: float) -> np.ndarray: + return np.asarray( + h0 + + model.drive_amplitude + * np.cos(model.drive_frequency * time) + * coupling, + dtype=np.complex128, + ) + + ground = diagonalize(h0).states[:, 0] + initial = np.outer(ground, ground.conj()) + period_steps = 12 + periods = 30 + dt = model.period / period_steps + run = PtTempoBackend().run( + hamiltonian, + coupling, + initial, + bath, + dt, + periods * period_steps, + memory_steps=3, + epsrel=1e-5, + ) + phase_residual = float( + np.linalg.norm( + run.result.density_matrices[-1] + - run.result.density_matrices[-1 - period_steps] + ) + ) + magnetization = [ + float(np.real(np.trace(coupling @ state))) + for state in run.result.density_matrices[-1 - period_steps : -1] + ] + return { + "method": "pt_tempo", + "converged": run.result.converged and phase_residual < 1e-3, + "diagnostics": { + **run.result.diagnostics, + "phase_residual": phase_residual, + "dt": dt, + "memory_steps": 3, + "epsrel": 1e-5, + }, + "model": asdict(model), + "bath": asdict(bath), + "data": [ + {"phase_index": index, "magnetization": value} + for index, value in enumerate(magnetization) + ], + } diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/spectra.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/spectra.py new file mode 100644 index 000000000..30f32bcc7 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/spectra.py @@ -0,0 +1,76 @@ +"""Hermitian spectra and symmetry-resolved bright transitions.""" + +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np +from numpy.typing import NDArray + +from .operators import ComplexMatrix + + +@dataclass(frozen=True) +class Spectrum: + energies: NDArray[np.float64] + states: ComplexMatrix + + +@dataclass(frozen=True) +class Transition: + source: int + target: int + frequency: float + weight: float + amplitude: complex + + +def diagonalize(hamiltonian: ComplexMatrix) -> Spectrum: + if not np.allclose(hamiltonian, hamiltonian.conj().T, atol=1e-12): + raise ValueError("hamiltonian must be Hermitian") + energies, states = np.linalg.eigh(hamiltonian) + for column in range(states.shape[1]): + pivot = int(np.argmax(np.abs(states[:, column]))) + phase = np.angle(states[pivot, column]) + states[:, column] *= np.exp(-1j * phase) + return Spectrum(energies.astype(np.float64), states) + + +def transitions( + source: Spectrum, + target: Spectrum, + operator: ComplexMatrix, + threshold: float = 1e-12, +) -> list[Transition]: + if operator.shape != (target.states.shape[0], source.states.shape[0]): + raise ValueError("transition operator has incompatible dimensions") + matrix = target.states.conj().T @ operator @ source.states + out: list[Transition] = [] + for target_index in range(len(target.energies)): + for source_index in range(len(source.energies)): + amplitude = matrix[target_index, source_index] + weight = float(abs(amplitude) ** 2) + if weight >= threshold: + out.append( + Transition( + source_index, + target_index, + float(target.energies[target_index] - source.energies[source_index]), + weight, + complex(amplitude), + ) + ) + return out + + +def ground_bright_transitions( + ground_sector: Spectrum, + opposite_sector: Spectrum, + cross_operator: ComplexMatrix, + threshold: float = 1e-12, +) -> list[Transition]: + candidates = transitions(ground_sector, opposite_sector, cross_operator, threshold) + return sorted( + [item for item in candidates if item.source == 0 and item.frequency > 0], + key=lambda item: item.frequency, + ) diff --git a/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/symmetry.py b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/symmetry.py new file mode 100644 index 000000000..31f770b90 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/src/floquet_if_manybody/symmetry.py @@ -0,0 +1,76 @@ +"""Exact Hilbert-space symmetry sectors for short open spin chains.""" + +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np +from numpy.typing import NDArray + +from .operators import ComplexMatrix, swap_operator + + +@dataclass(frozen=True) +class Sector: + name: str + isometry: ComplexMatrix + + def __post_init__(self) -> None: + gram = self.isometry.conj().T @ self.isometry + if not np.allclose(gram, np.eye(gram.shape[0]), atol=1e-13): + raise ValueError(f"{self.name} isometry is not orthonormal") + + @property + def dimension(self) -> int: + return int(self.isometry.shape[1]) + + +def _eigenspace(operator: ComplexMatrix, eigenvalue: float, name: str) -> Sector: + values, vectors = np.linalg.eigh(operator) + selected = vectors[:, np.isclose(values, eigenvalue, atol=1e-12)] + return Sector(name, selected) + + +def n2_sectors() -> tuple[Sector, Sector]: + swap = swap_operator(0, 1, 2) + singlet = _eigenspace(swap, -1, "singlet") + triplet = _eigenspace(swap, +1, "triplet") + return singlet, triplet + + +def n3_reflection_sectors() -> tuple[Sector, Sector]: + return reflection_sectors(3) + + +def reflection_sectors(n: int) -> tuple[Sector, Sector]: + """Return odd/even sectors of full spatial reflection for an open chain.""" + if n < 2: + raise ValueError("reflection sectors require at least two sites") + reflection = np.eye(2**n, dtype=np.complex128) + for site in range(n // 2): + reflection = swap_operator(site, n - 1 - site, n) @ reflection + odd = _eigenspace(reflection, -1, "odd") + even = _eigenspace(reflection, +1, "even") + return odd, even + + +def n4_reflection_sectors() -> tuple[Sector, Sector]: + return reflection_sectors(4) + + +def project(operator: ComplexMatrix, sector: Sector) -> ComplexMatrix: + if operator.shape[0] != sector.isometry.shape[0]: + raise ValueError("operator and sector dimensions do not match") + return sector.isometry.conj().T @ operator @ sector.isometry + + +def cross_project( + operator: ComplexMatrix, left: Sector, right: Sector +) -> NDArray[np.complex128]: + return left.isometry.conj().T @ operator @ right.isometry + + +def sector_residual(operator: ComplexMatrix, sector: Sector) -> float: + identity = np.eye(operator.shape[0], dtype=np.complex128) + projector = sector.isometry @ sector.isometry.conj().T + return float(np.linalg.norm((identity - projector) @ operator @ sector.isometry)) diff --git a/tracks/mps/solutions/Ranger-123/tests/heat_valve_fixtures.py b/tracks/mps/solutions/Ranger-123/tests/heat_valve_fixtures.py new file mode 100644 index 000000000..dfe5be707 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/heat_valve_fixtures.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +from copy import deepcopy +from typing import Any + + +def valid_heat_valve_manifest() -> dict[str, Any]: + selected_points: list[dict[str, Any]] = [] + points: list[dict[str, Any]] = [] + flank_heat = {1: 1.0, 2: 2.0, 3: 6.0} + for n in (1, 2, 3): + for role, xi in zip( + ("lower_flank", "minimum", "upper_flank"), + (2.0, 2.2, 2.4), + strict=True, + ): + selected_points.append({"n": n, "xi": xi}) + heat = 0.05 if role == "minimum" else flank_heat[n] + residue = 0.05 if role == "minimum" else flank_heat[n] + points.append( + { + "complete": True, + "converged": True, + "point": {"n": n, "xi": xi}, + "model": { + "n": n, + "j": 1.0, + "omega": 1.0, + "drive_amplitude": 1.5 * xi, + "drive_frequency": 3.0, + "normalization": "bounded", + "drive_normalization": "per_spin", + }, + "bath": { + "alpha": 0.05, + "cutoff": 2.5, + "temperature": 0.0, + }, + "numerics": { + "steps_per_period": 60, + "tolerance": 1e-6, + "phase_samples": 3, + "delay_periods": 12, + "pole_count": 8, + }, + "diagnostics": { + "trace_error": 1e-10, + "hermiticity_error": 1e-10, + "minimum_density_eigenvalue": 0.01, + "fixed_point_residual": 1e-10, + "connected_tail": 1e-3, + "maximum_eigenpair_residual": 1e-12, + "maximum_pole_modulus": 0.9, + }, + "pole_fit": { + "reconstruction_residual": 1e-3, + "condition_number": 10.0, + }, + "poles": [ + { + "eigenvalue": { + "real": 0.85, + "imag": 0.1, + "abs": 0.9, + }, + "decay_rate": 0.05, + "quasifrequency": 0.1, + "eigenpair_residual": 1e-12, + "residue": {"abs": residue}, + } + ], + "frequency": [0.0, 1.0, 2.0, 3.0], + "continuous": [0.0, heat, heat / 2, 0.0], + "integrated_absolute_heat": heat, + "dominant_residue": {"abs": residue}, + "visible_residue_weight": residue, + } + ) + return deepcopy( + { + "complete": True, + "selected_points": selected_points, + "points": points, + "markov_comparison": {"passed": True}, + } + ) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_adaptive.py b/tracks/mps/solutions/Ranger-123/tests/test_adaptive.py new file mode 100644 index 000000000..52eb302f3 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_adaptive.py @@ -0,0 +1,312 @@ +from __future__ import annotations + +from dataclasses import asdict +from pathlib import Path +from typing import Any + +import numpy as np + +from floquet_if_manybody.adaptive import ( + AdaptiveSchedule, + UniformAdaptiveSchedule, + run_adaptive, + run_uniform_adaptive, + run_uniform_compression_audit, +) +from floquet_if_manybody.convergence import ConvergenceCache, fingerprint +from floquet_if_manybody.n3_heat import N3HeatPoint + + +def _fake_runner(counter: dict[str, int]): + def run(point: N3HeatPoint, cache: ConvergenceCache | None) -> dict[str, Any]: + key = fingerprint(asdict(point), "fake") + if cache is not None and cache.contains(key): + return cache.load(key) + counter["calls"] += 1 + error = ( + (1 / point.steps_per_period) ** 2 + + np.exp(-point.memory_steps) + + point.epsrel + ) + grid = np.linspace(0, 1, 9) + payload: dict[str, Any] = { + "fingerprint": key, + "complete": True, + "converged": True, + "diagnostics": { + "phase_residual": 1e-5, + "trace_error": 1e-8, + "minimum_density_eigenvalue": 0.0, + "connected_tail_amplitude": 1e-4, + }, + "phase_state": {"real": [1 - error, error], "imag": [0.0, 0.0]}, + "correlation": { + "delay": grid.tolist(), + "connected": { + "real": (np.exp(-grid) + error).tolist(), + "imag": np.zeros_like(grid).tolist(), + }, + }, + "frequency": grid.tolist(), + "continuous": (grid * (1 + error)).tolist(), + } + if cache is not None: + cache.store(key, payload) + return cache.load(key) + return payload + + return run + + +def test_adaptive_refines_all_controls_and_resumes(tmp_path: Path) -> None: + counter = {"calls": 0} + runner = _fake_runner(counter) + cache = ConvergenceCache(tmp_path) + point = N3HeatPoint( + steps_per_period=4, + memory_steps=1, + epsrel=0.1, + steady_periods=1, + delay_periods=1, + frequency_points=9, + ) + schedule = AdaptiveSchedule( + memory_steps=(1, 3, 6), + steps_per_period=(4, 8, 16), + epsrel=(0.1, 0.01, 0.001), + state_threshold=0.1, + correlation_threshold=0.1, + heat_threshold=0.1, + ) + result = run_adaptive(point, schedule, runner, cache) + assert result.converged + assert result.final_point.memory_steps >= 3 + assert result.final_point.steps_per_period >= 8 + assert result.final_point.epsrel <= 0.01 + assert result.final_point.memory_steps >= 12 + assert {record.parameter for record in result.evidence} == { + "memory_steps", + "steps_per_period", + "epsrel", + } + first_calls = counter["calls"] + resumed = run_adaptive(point, schedule, runner, cache) + assert resumed.converged + assert counter["calls"] == first_calls + + +def test_adaptive_reports_resource_ceiling() -> None: + counter = {"calls": 0} + point = N3HeatPoint( + steps_per_period=4, + memory_steps=1, + epsrel=0.1, + steady_periods=1, + delay_periods=1, + frequency_points=9, + ) + schedule = AdaptiveSchedule( + memory_steps=(1, 2), + steps_per_period=(4, 8), + epsrel=(0.1, 0.05), + state_threshold=1e-12, + correlation_threshold=1e-12, + heat_threshold=1e-12, + ) + result = run_adaptive(point, schedule, _fake_runner(counter), None) + assert not result.converged + assert result.status == "resource_ceiling" + assert result.failed_parameter == "memory_steps" + + +def test_uniform_adaptive_refines_compression_timestep_and_phase() -> None: + calls: list[tuple[float, int, int | None]] = [] + + def runner( + point: N3HeatPoint, cache: ConvergenceCache | None + ) -> dict[str, Any]: + del cache + calls.append((point.epsrel, point.steps_per_period, point.phase_samples)) + error = point.epsrel + 1 / point.steps_per_period**2 + 0.02 / ( + point.phase_samples or point.steps_per_period + ) + grid = np.linspace(0, 1, 9) + return { + "fingerprint": fingerprint(asdict(point), "uniform-fake"), + "complete": True, + "converged": True, + "diagnostics": { + "phase_residual": 1e-5, + "fixed_point_residual": 1e-5, + "trace_error": 1e-8, + "hermiticity_error": 1e-8, + "minimum_density_eigenvalue": 0.0, + "connected_tail_amplitude": 1e-4, + "bond_dimension": int(5 - np.log10(point.epsrel)), + }, + "phase_state": {"real": [1 - error, error], "imag": [0.0, 0.0]}, + "correlation": { + "delay": grid.tolist(), + "connected": { + "real": (np.exp(-grid) + error).tolist(), + "imag": np.zeros_like(grid).tolist(), + }, + }, + "frequency": grid.tolist(), + "continuous": (grid * (1 + error)).tolist(), + } + + point = N3HeatPoint( + backend="uniform_tempo", + steps_per_period=12, + phase_samples=3, + epsrel=1e-3, + delay_periods=1, + frequency_points=9, + ) + schedule = UniformAdaptiveSchedule( + steps_per_period=(12, 18), + tolerances=(1e-3, 1e-4), + phase_samples=(3, 6), + state_threshold=0.1, + correlation_threshold=0.1, + heat_threshold=0.1, + ) + result = run_uniform_adaptive(point, schedule, runner, None) + assert result.converged + assert result.final_point.steps_per_period == 18 + assert result.final_point.epsrel == 1e-4 + assert result.final_point.phase_samples == 6 + assert {record.parameter for record in result.evidence} == { + "epsrel", + "steps_per_period", + "phase_samples", + } + assert [record.parameter for record in result.evidence] == [ + "epsrel", + "epsrel", + "steps_per_period", + "phase_samples", + ] + assert all(record.refined_bond_dimension is not None for record in result.evidence) + assert calls == [ + (1e-3, 12, 3), + (1e-4, 12, 3), + (1e-3, 18, 3), + (1e-4, 18, 3), + (1e-4, 18, 6), + ] + + +def test_uniform_schedule_requires_common_phase_divisors() -> None: + with np.testing.assert_raises_regex(ValueError, "phase_samples"): + UniformAdaptiveSchedule( + steps_per_period=(60, 90), + tolerances=(1e-6, 3e-7), + phase_samples=(3, 8), + ) + + +def test_uniform_adaptive_skips_compression_unstable_coarse_grid() -> None: + def runner( + point: N3HeatPoint, cache: ConvergenceCache | None + ) -> dict[str, Any]: + del cache + compression_scale = 10.0 if point.steps_per_period == 12 else 0.01 + error = ( + compression_scale * point.epsrel + + 1 / point.steps_per_period**2 + + 0.001 / (point.phase_samples or 3) + ) + grid = np.linspace(0, 1, 9) + return { + "fingerprint": fingerprint(asdict(point), "uniform-skip-fake"), + "complete": True, + "converged": True, + "diagnostics": { + "phase_residual": 1e-5, + "fixed_point_residual": 1e-5, + "trace_error": 1e-8, + "hermiticity_error": 1e-8, + "minimum_density_eigenvalue": 0.0, + "connected_tail_amplitude": 1e-4, + "bond_dimension": 10, + }, + "phase_state": {"real": [1 - error, error], "imag": [0.0, 0.0]}, + "correlation": { + "delay": grid.tolist(), + "connected": { + "real": (np.exp(-grid) + error).tolist(), + "imag": np.zeros_like(grid).tolist(), + }, + }, + "frequency": grid.tolist(), + "continuous": (grid * (1 + error)).tolist(), + } + + point = N3HeatPoint( + backend="uniform_tempo", + steps_per_period=12, + phase_samples=3, + epsrel=0.1, + delay_periods=1, + frequency_points=9, + ) + result = run_uniform_adaptive( + point, + UniformAdaptiveSchedule( + steps_per_period=(12, 18, 24), + tolerances=(0.1, 0.01), + phase_samples=(3, 6), + state_threshold=0.1, + correlation_threshold=0.1, + heat_threshold=0.1, + ), + runner, + None, + ) + assert result.converged + assert result.final_point.steps_per_period == 24 + assert any( + item.parameter == "epsrel" + and item.coarse_steps_per_period == 12 + and not item.passed + for item in result.evidence + ) + assert any( + item.parameter == "steps_per_period" + and item.coarse_steps_per_period == 18 + and item.refined_steps_per_period == 24 + and item.passed + for item in result.evidence + ) + + +def test_uniform_compression_audit_never_claims_full_convergence() -> None: + point = N3HeatPoint( + backend="uniform_tempo", + steps_per_period=12, + phase_samples=3, + epsrel=1e-3, + delay_periods=1, + frequency_points=9, + ) + result = run_uniform_compression_audit( + point, + UniformAdaptiveSchedule( + steps_per_period=(12, 18), + tolerances=(1e-3, 1e-4), + phase_samples=(3, 6), + state_threshold=0.1, + correlation_threshold=0.1, + heat_threshold=0.1, + ), + _fake_runner({"calls": 0}), + None, + ) + assert not result.converged + assert result.status == "resource_ceiling" + assert result.failed_parameter == "steps_per_period" + assert len(result.evidence) == 1 + assert result.evidence[0].parameter == "epsrel" + assert result.evidence[0].passed diff --git a/tracks/mps/solutions/Ranger-123/tests/test_bath.py b/tracks/mps/solutions/Ranger-123/tests/test_bath.py new file mode 100644 index 000000000..e62b61acc --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_bath.py @@ -0,0 +1,12 @@ +from numpy.testing import assert_allclose + +from floquet_if_manybody.bath import bath_correlation +from floquet_if_manybody.config import BathConfig + + +def test_zero_temperature_ohmic_correlation(): + bath = BathConfig(alpha=0.05, cutoff=2.5) + for time in [0, 0.1, 1.0, 3.0]: + expected = bath.alpha * bath.cutoff**2 / (1 + 1j * bath.cutoff * time) ** 2 + assert_allclose(bath_correlation(time, bath), expected) + assert_allclose(bath_correlation(-time, bath), expected.conjugate()) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_cli.py b/tracks/mps/solutions/Ranger-123/tests/test_cli.py new file mode 100644 index 000000000..62b707b57 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_cli.py @@ -0,0 +1,70 @@ +import json +from pathlib import Path + +from heat_valve_fixtures import valid_heat_valve_manifest + +from floquet_if_manybody.cli import build_parser, main + + +def test_quick_baseline_and_audit(tmp_path): + results = tmp_path / "results" + figures = tmp_path / "figures" + assert main(["baselines", "--output", str(results), "--figures", str(figures), "--quick"]) == 0 + assert main(["audit", str(results), "--allow-unconverged"]) == 0 + for path in results.glob("*.json"): + payload = json.loads(path.read_text()) + assert payload["schema_version"] == 1 + assert payload["config_hash"] + assert payload["method"] + assert "environment" in payload + assert len(list(figures.glob("*.pdf"))) == 4 + assert len(list(figures.glob("*.png"))) == 4 + + +def test_audit_ignores_artifact_provenance(tmp_path) -> None: + results = tmp_path / "results" + results.mkdir() + (results / "ARTIFACT_PROVENANCE.json").write_text( + json.dumps({"schema_version": 1, "artifacts": []}), + encoding="utf-8", + ) + assert ( + main( + [ + "baselines", + "--output", + str(results), + "--figures", + str(tmp_path / "figures"), + "--quick", + ] + ) + == 0 + ) + assert main(["audit", str(results), "--allow-unconverged"]) == 0 + + +def test_publication_commands_default_to_uniform_tempo() -> None: + parser = build_parser() + for command in ("n3-heat-grid", "error-map", "model-comparison"): + arguments = parser.parse_args([command]) + assert arguments.exact_backend == "uniform_tempo" + explicit = parser.parse_args([command, "--exact-backend", "oqupy"]) + assert explicit.exact_backend == "oqupy" + + +def test_heat_valve_cli_defaults() -> None: + arguments = build_parser().parse_args(["heat-valve"]) + assert arguments.output == Path("results/heat-valve") + assert arguments.cache == Path("results/cache/uniform_tempo") + assert arguments.figures == Path("figures/heat-valve") + assert not arguments.full + + +def test_heat_valve_audit_command_accepts_a_valid_manifest(tmp_path) -> None: + manifest = valid_heat_valve_manifest() + (tmp_path / "heat_valve_manifest.json").write_text( + json.dumps(manifest), + encoding="utf-8", + ) + assert main(["heat-valve-audit", str(tmp_path)]) == 0 diff --git a/tracks/mps/solutions/Ranger-123/tests/test_config.py b/tracks/mps/solutions/Ranger-123/tests/test_config.py new file mode 100644 index 000000000..0a97a9e79 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_config.py @@ -0,0 +1,25 @@ +import pytest + +from floquet_if_manybody.config import ModelConfig, RunConfig + + +def test_default_normalization_and_units(): + cfg = RunConfig() + assert cfg.model.omega == 1.0 + assert cfg.model.normalization == "bounded" + assert cfg.model.eta == 1 / cfg.model.n + + +def test_normalizations(): + assert ModelConfig(n=3, normalization="bounded").eta == pytest.approx(1 / 3) + assert ModelConfig(n=3, normalization="kac").eta == pytest.approx(1 / 3**0.5) + assert ModelConfig(n=3, normalization="collective").eta == 1 + + +def test_invalid_parameters_rejected(): + with pytest.raises(ValueError, match="omega"): + ModelConfig(omega=0) + + +def test_model_config_accepts_n4(): + assert ModelConfig(n=4).n == 4 diff --git a/tracks/mps/solutions/Ranger-123/tests/test_convergence.py b/tracks/mps/solutions/Ranger-123/tests/test_convergence.py new file mode 100644 index 000000000..c929bd0a4 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_convergence.py @@ -0,0 +1,35 @@ +import numpy as np +import pytest + +from floquet_if_manybody.convergence import ( + ConvergenceCache, + atomic_write_result, + curve_residual, + fingerprint, + state_residual, +) + + +def test_fingerprint_is_order_independent_and_commit_sensitive(): + first = fingerprint({"alpha": 0.1, "dt": 0.2}, "abc") + second = fingerprint({"dt": 0.2, "alpha": 0.1}, "abc") + assert first == second + assert first != fingerprint({"alpha": 0.1, "dt": 0.2}, "def") + + +def test_curve_and_state_residuals(): + grid = np.linspace(0, 1, 11) + assert curve_residual(grid, grid, grid, grid) == 0 + assert state_residual(np.eye(2), np.eye(2)) == 0 + with pytest.raises(ValueError, match="grid"): + curve_residual(np.arange(3.0), np.ones(3), np.arange(4.0), np.ones(4)) + + +def test_cache_rejects_incomplete_or_wrong_key(tmp_path): + key = fingerprint({"x": 1}, "abc") + cache = ConvergenceCache(tmp_path) + cache.store(key, {"value": 3}) + assert cache.load(key)["value"] == 3 + atomic_write_result(cache.path_for(key), {"fingerprint": key, "complete": False}) + with pytest.raises(ValueError, match="incomplete"): + cache.load(key) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_correlations.py b/tracks/mps/solutions/Ranger-123/tests/test_correlations.py new file mode 100644 index 000000000..d82451e4a --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_correlations.py @@ -0,0 +1,26 @@ +import numpy as np +from numpy.testing import assert_allclose + +from floquet_if_manybody.correlations import coherent_decomposition + + +def test_single_cosine_has_one_coherent_harmonic(): + steps = 128 + omega = 1.7 + amplitude = 0.6 + period = 2 * np.pi / omega + times = np.arange(steps) * period / steps + delays = np.linspace(0, 3 * period, 301) + coherent, peaks = coherent_decomposition(amplitude * np.cos(omega * times), omega, delays) + assert len(peaks) == 1 + assert peaks[0].harmonic == 1 + assert_allclose(peaks[0].correlation_weight, amplitude**2 / 2, atol=1e-14) + assert_allclose(coherent, amplitude**2 / 2 * np.cos(omega * delays), atol=1e-14) + + +def test_half_period_antisymmetry_removes_even_harmonics(): + steps = 128 + times = np.arange(steps) * 2 * np.pi / steps + signal = 0.4 * np.cos(times) + 0.1 * np.cos(3 * times) + _, peaks = coherent_decomposition(signal, 1.0, times) + assert {peak.harmonic for peak in peaks} == {1, 3} diff --git a/tracks/mps/solutions/Ranger-123/tests/test_dark_channels.py b/tracks/mps/solutions/Ranger-123/tests/test_dark_channels.py new file mode 100644 index 000000000..c355cb6a7 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_dark_channels.py @@ -0,0 +1,31 @@ +import numpy as np +from numpy.testing import assert_allclose + +from floquet_if_manybody.dark_channels import ( + dark_candidates, + floquet_matrix_elements, + harmonic_sum_rule, + period_variance, +) +from floquet_if_manybody.floquet import solve_floquet + + +def test_static_system_has_only_zero_harmonic_matrix_elements(): + hamiltonian = np.diag([-0.2, 0.3]).astype(complex) + operator = np.array([[0, 1], [1, 0]], dtype=complex) + solution = solve_floquet(lambda _time: hamiltonian, period=1.0, steps=64) + records = floquet_matrix_elements(solution, operator, harmonic_cutoff=2, threshold=1e-14) + bright = [record for record in records if record.weight > 1e-12] + assert {record.harmonic for record in bright} == {0} + assert_allclose(sorted(record.weight for record in bright), [1, 1], atol=1e-12) + assert harmonic_sum_rule(solution, operator, 2) < 1e-12 + + +def test_dark_candidates_and_variance(): + hamiltonian = np.diag([-0.2, 0.3]).astype(complex) + operator = np.diag([1, -1]).astype(complex) + solution = solve_floquet(lambda _time: hamiltonian, period=1.0, steps=32) + records = floquet_matrix_elements(solution, operator, 1) + assert len(dark_candidates(records, 1e-8)) > 0 + densities = np.repeat(np.eye(2, dtype=complex)[None, :, :] / 2, 8, axis=0) + assert_allclose(period_variance(densities, operator), 1) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_error_map.py b/tracks/mps/solutions/Ranger-123/tests/test_error_map.py new file mode 100644 index 000000000..db950bcf6 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_error_map.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +from copy import deepcopy + +import numpy as np +import pytest + +from floquet_if_manybody.error_map import ( + audit_grid_manifest, + build_error_record, + correlation_error, + heat_error, + trace_distance, +) + + +def _result(method: str) -> dict[str, object]: + return { + "method": method, + "converged": True, + "model_hash": "same", + "model": {"normalization": "bounded"}, + "phase_state": { + "real": [[1.0, 0.0], [0.0, 0.0]], + "imag": [[0.0, 0.0], [0.0, 0.0]], + }, + "correlation": { + "delay": [0.0, 1.0], + "connected": {"real": [1.0, 0.5], "imag": [0.0, 0.0]}, + }, + "frequency": [0.0, 1.0], + "continuous": [0.0, 1.0], + "evidence": [], + } + + +def test_trace_distance_diagonal_example() -> None: + first = np.diag([0.75, 0.25]).astype(complex) + second = np.diag([0.5, 0.5]).astype(complex) + assert trace_distance(first, second) == pytest.approx(0.25) + + +def test_identical_curves_have_zero_error() -> None: + grid = np.linspace(0, 1, 5) + curve = np.exp(-grid) + 1j * grid + assert correlation_error(grid, curve, grid, curve) == pytest.approx(0.0) + assert heat_error(grid, curve.real, grid, curve.real) == pytest.approx(0.0) + + +@pytest.mark.parametrize("field", ["model_hash", "normalization", "frequency"]) +def test_build_record_rejects_incompatible_inputs(field: str) -> None: + exact = _result("pt_tempo_multitime") + markov = deepcopy(exact) + markov["method"] = "floquet_markov_qr" + if field == "model_hash": + markov["model_hash"] = "different" + elif field == "normalization": + markov["model"]["normalization"] = "kac" # type: ignore[index] + else: + markov["frequency"] = [0.0, 2.0] + with pytest.raises(ValueError, match=field): + build_error_record(exact, markov) + + +def test_build_record_rejects_unconverged_exact_input() -> None: + exact = _result("pt_tempo_multitime") + exact["converged"] = False + with pytest.raises(ValueError, match="converged"): + build_error_record(exact, _result("floquet_markov_qr")) + + +@pytest.mark.parametrize( + "method", + ["pt_tempo_multitime", "uniform_tempo_floquet_multitime"], +) +def test_build_record_accepts_approved_process_tensor_methods(method: str) -> None: + record = build_error_record(_result(method), _result("floquet_markov_qr")) + assert record["status"] == "converged" + assert record["exact_method"] == method + + +def test_grid_audit_accepts_explicit_resource_masks() -> None: + alphas = (0.025, 0.05, 0.1) + ratios = (0.75, 1.0, 1.25) + points = [ + { + "alpha": alpha, + "drive_ratio": ratio, + "status": ( + "resource_ceiling" if (alpha, ratio) == (0.1, 1.25) else "converged" + ), + "metrics": None if (alpha, ratio) == (0.1, 1.25) else {"heat": 0.1}, + } + for alpha in alphas + for ratio in ratios + ] + audit = audit_grid_manifest({"points": points}, alphas, ratios) + assert audit["complete"] + assert audit["masked_points"] == 1 diff --git a/tracks/mps/solutions/Ranger-123/tests/test_fig3_validation.py b/tracks/mps/solutions/Ranger-123/tests/test_fig3_validation.py new file mode 100644 index 000000000..0dd5b9955 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_fig3_validation.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import importlib.util +from pathlib import Path + +import numpy as np + + +def _module(): + path = Path(__file__).parents[1] / "scripts" / "run_fig3_validation.py" + spec = importlib.util.spec_from_file_location("run_fig3_validation", path) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def test_reference_names_match_published_archive() -> None: + module = _module() + assert "ω_d_1.5" in module._reference_name(1.5) + assert module.REFERENCE_FREQUENCY.shape == (3000,) + assert np.isclose(module.REFERENCE_FREQUENCY[-1], 15.0) + + +def test_comparison_metrics_separate_shape_and_amplitude() -> None: + module = _module() + reference = np.exp(-module.REFERENCE_FREQUENCY) + metrics = module.comparison_metrics(reference, 2 * reference) + assert np.isclose(metrics["continuous_relative_l1"], 0.5) + assert np.isclose(metrics["normalized_shape_relative_l1"], 0.0) + assert np.isclose(metrics["integrated_magnitude_ratio"], 2.0) + + +def test_plot_summary_writes_both_publication_formats(tmp_path: Path) -> None: + module = _module() + curve = np.exp(-module.REFERENCE_FREQUENCY) + results = [] + for drive_frequency in (1.0, 1.5, 2.0): + results.append( + { + "model": {"drive_frequency": drive_frequency}, + "frequency": module.REFERENCE_FREQUENCY.tolist(), + "reference_continuous": curve.tolist(), + "continuous": curve.tolist(), + "delta_peaks": [ + {"frequency": drive_frequency, "harmonic": 1, "weight": 1.0} + ], + "metrics": {"normalized_shape_relative_l1": 0.0}, + } + ) + stem = tmp_path / "summary" + module.plot_summary(results, stem) + assert stem.with_suffix(".png").stat().st_size > 0 + assert stem.with_suffix(".pdf").stat().st_size > 0 diff --git a/tracks/mps/solutions/Ranger-123/tests/test_finite_memory.py b/tracks/mps/solutions/Ranger-123/tests/test_finite_memory.py new file mode 100644 index 000000000..b494a64b9 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_finite_memory.py @@ -0,0 +1,24 @@ +import numpy as np +from numpy.testing import assert_allclose + +from floquet_if_manybody.backends.finite_memory import FiniteMemoryBackend +from floquet_if_manybody.config import BathConfig + + +def test_zero_bath_matches_unitary_evolution(): + h = np.array([[0, 0.4], [0.4, 0]], dtype=complex) + coupling = np.diag([1, -1]).astype(complex) + rho0 = np.array([[1, 0], [0, 0]], dtype=complex) + dt = 0.05 + result = FiniteMemoryBackend().run( + lambda _time: h, coupling, rho0, BathConfig(alpha=0), dt, 10, 2 + ) + unitary = expm_for_test(h, 10 * dt) + assert_allclose(result.density_matrices[-1], unitary @ rho0 @ unitary.conj().T, atol=1e-12) + assert result.diagnostics["trace_error"] < 1e-12 + + +def expm_for_test(hamiltonian, time): + from scipy.linalg import expm + + return expm(-1j * hamiltonian * time) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_floquet.py b/tracks/mps/solutions/Ranger-123/tests/test_floquet.py new file mode 100644 index 000000000..991fe5a96 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_floquet.py @@ -0,0 +1,30 @@ +import numpy as np +from numpy.testing import assert_allclose +from scipy.linalg import expm + +from floquet_if_manybody.floquet import solve_floquet + + +def test_static_floquet_matches_matrix_exponential(): + h = np.array([[0.2, 0.3], [0.3, -0.2]], dtype=complex) + period = 1.7 + solution = solve_floquet(lambda _time: h, period, 32) + assert_allclose(solution.propagator, expm(-1j * h * period), atol=1e-13) + assert solution.unitarity_residual < 1e-13 + assert solution.eigen_residual < 1e-12 + assert np.all(solution.quasienergies >= -np.pi / period) + assert np.all(solution.quasienergies < np.pi / period) + + +def test_midpoint_rule_converges_quadratically(): + period = 2 * np.pi + + def h(t): + return np.array([[0.4, 0.2 * np.cos(t)], [0.2 * np.cos(t), -0.4]]) + + reference = solve_floquet(h, period, 4096).propagator + errors = [ + np.linalg.norm(solve_floquet(h, period, steps).propagator - reference) + for steps in (32, 64) + ] + assert errors[0] / errors[1] > 3.8 diff --git a/tracks/mps/solutions/Ranger-123/tests/test_floquet_markov.py b/tracks/mps/solutions/Ranger-123/tests/test_floquet_markov.py new file mode 100644 index 000000000..4e0180ba1 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_floquet_markov.py @@ -0,0 +1,15 @@ +import numpy as np + +from floquet_if_manybody.backends.floquet_markov import FloquetMarkovBackend +from floquet_if_manybody.config import BathConfig + + +def test_markov_result_is_trace_preserving_and_labeled(): + h = np.array([[0.5, 0.2], [0.2, -0.5]], dtype=complex) + s = np.diag([1, -1]).astype(complex) + result = FloquetMarkovBackend().run( + lambda _time: h, s, BathConfig(alpha=0.01), 2 * np.pi, 64, 2 + ) + assert result.method == "floquet_markov" + assert result.diagnostics["trace_error"] < 1e-12 + assert result.diagnostics["minimum_population"] >= -1e-12 diff --git a/tracks/mps/solutions/Ranger-123/tests/test_heat_current.py b/tracks/mps/solutions/Ranger-123/tests/test_heat_current.py new file mode 100644 index 000000000..5e05ae22f --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_heat_current.py @@ -0,0 +1,34 @@ +import numpy as np +from numpy.testing import assert_allclose + +from floquet_if_manybody.config import BathConfig +from floquet_if_manybody.correlations import CorrelationResult, DeltaCorrelationPeak +from floquet_if_manybody.heat_current import heat_current_spectrum + + +def test_coherent_delta_peak_is_not_smeared_into_continuum(): + delays = np.linspace(0, 20, 2001) + weight = 0.18 + frequency = 1.7 + coherent = weight * np.cos(frequency * delays) + correlation = CorrelationResult( + delays, + coherent.astype(complex), + np.zeros_like(delays, dtype=complex), + coherent, + (DeltaCorrelationPeak(1, frequency, weight),), + "analytic_test", + {}, + ) + bath = BathConfig(alpha=0.05, cutoff=2.5) + result = heat_current_spectrum(correlation, bath, np.linspace(0, 4, 101)) + assert_allclose(result.continuous, 0) + expected = ( + np.pi + * bath.alpha + * frequency + * np.exp(-frequency / bath.cutoff) + * frequency + * weight + ) + assert_allclose(result.delta_peaks[0].weight, expected) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_heat_valve.py b/tracks/mps/solutions/Ranger-123/tests/test_heat_valve.py new file mode 100644 index 000000000..591ce4ddc --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_heat_valve.py @@ -0,0 +1,193 @@ +from __future__ import annotations + +from dataclasses import asdict +from pathlib import Path + +import numpy as np +import pytest +from numpy.testing import assert_allclose +from scipy.special import jn_zeros + +import floquet_if_manybody.heat_valve as heat_valve_module +from floquet_if_manybody.backends.uniform_tempo import UniformTempoResult +from floquet_if_manybody.convergence import ConvergenceCache +from floquet_if_manybody.correlations import CorrelationResult +from floquet_if_manybody.heat_valve import ( + HeatValvePoint, + ValveNumerics, + build_heat_valve_manifest, + isolated_valve_scan, + prepare_heat_valve_point, + run_uniform_valve_point, +) + + +class FakePoleBackend: + def __init__(self, **_kwargs: object) -> None: + pass + + def run_periodic( + self, + h0: np.ndarray, + coupling: np.ndarray, + model: object, + _bath: object, + controls: object, + *, + drive_operator: np.ndarray, + ) -> UniformTempoResult: + assert_allclose(drive_operator, 3 * coupling) + dimension = h0.shape[0] + period = model.period + dt = period / controls.steps_per_period + delays = np.arange(controls.delay_steps + 1) * dt + eigenvalues = np.array( + [1.0, 0.85, 0.75, 0.65, 0.55, 0.45, 0.4, 0.35], + dtype=complex, + ) + residues = np.linspace(0.7, 0.1, 7).astype(complex) + connected = np.zeros(len(delays), dtype=complex) + for index, delay in enumerate(delays): + scaled = delay / period + if np.isclose(scaled, round(scaled), atol=1e-10): + order = round(scaled) + connected[index] = np.sum(residues * eigenvalues[1:] ** order) + correlation = CorrelationResult( + delays=delays, + total=connected, + connected=connected, + coherent=np.zeros(len(delays)), + delta_peaks=(), + method="uniform_tempo_floquet_multitime", + metadata={"dt": dt}, + ) + state = np.eye(dimension, dtype=complex) / dimension + return UniformTempoResult( + method="uniform_tempo_floquet_multitime", + floquet_state=state, + phase_states=np.repeat( + state[None, ...], + controls.phase_samples, + axis=0, + ), + correlation=correlation, + diagnostics={ + "trace_error": 1e-12, + "hermiticity_error": 1e-12, + "minimum_density_eigenvalue": 1 / dimension, + "fixed_point_residual": 1e-12, + "floquet_transfer_residual": 1e-12, + }, + metadata={ + "dt": dt, + "period_steps": controls.steps_per_period, + "phase_samples": controls.phase_samples, + "bond_dimension": 2, + "tolerance": controls.tolerance, + "julia_version": "1.12.6", + "uniform_tempo_revision": "test", + "manifest_sha256": "a" * 64, + "process_tensor_cache_hit": 0, + "transfer_dimension": 48, + "pole_count": 8, + }, + transfer_eigenvalues=eigenvalues, + transfer_eigenpair_residuals=np.full(8, 1e-12), + ) + + +def test_heat_valve_uses_common_physical_drive_for_all_sizes() -> None: + prepared = [ + prepare_heat_valve_point(HeatValvePoint(n=n, xi=2.2)) + for n in (1, 2, 3) + ] + assert_allclose( + [item.model.drive_amplitude for item in prepared], + [prepared[0].model.drive_frequency * 2.2 / 2] * 3, + ) + assert_allclose( + [ + np.linalg.norm(item.drive) / np.linalg.norm(item.coupling) + for item in prepared + ], + [1, 2, 3], + ) + + +def test_single_spin_high_frequency_gap_minimum_tracks_first_bessel_zero() -> None: + xis = np.linspace(2.2, 2.6, 41) + manifest = isolated_valve_scan( + HeatValvePoint( + n=1, + j=0.0, + drive_frequency=6.0, + xi=float(xi), + floquet_steps=360, + ) + for xi in xis + ) + minimum = min(manifest["points"], key=lambda item: item["cat_gap"]) + assert abs(minimum["xi"] - jn_zeros(0, 1)[0]) <= xis[1] - xis[0] + + +def test_interacting_scan_returns_resolved_cat_diagnostics() -> None: + manifest = isolated_valve_scan( + HeatValvePoint(n=n, xi=xi, drive_frequency=3.0, floquet_steps=180) + for n in (2, 3) + for xi in (2.2, 2.4, 2.6) + ) + assert manifest["complete"] + assert len(manifest["points"]) == 6 + for point in manifest["points"]: + assert point["cat_overlap"] >= 0.5 + assert point["cat_gap"] >= 0 + assert point["cat_brightness"] >= 0 + assert point["unitarity_residual"] < 1e-10 + assert point["floquet_eigen_residual"] < 1e-10 + + +def test_uniform_valve_point_records_poles_residues_and_fixed_controls( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr( + heat_valve_module, + "UniformTempoBackend", + FakePoleBackend, + ) + result = run_uniform_valve_point( + HeatValvePoint(n=3, xi=2.4, drive_frequency=3.0), + ValveNumerics( + steps_per_period=60, + tolerance=1e-6, + phase_samples=3, + delay_periods=12, + pole_count=8, + ), + ConvergenceCache(tmp_path), + source_revision="test", + ) + assert result["model"]["drive_frequency"] == 3.0 + assert result["bath"]["cutoff"] == 2.5 + assert result["pole_fit"]["reconstruction_residual"] < 0.05 + assert len(result["poles"]) == 7 + + +def test_manifest_selects_minimum_and_two_resolved_flanks() -> None: + records = [] + for n in (1, 2, 3): + for xi, gap in ((2.0, 0.2), (2.2, 0.01), (2.4, 0.3)): + records.append( + { + **asdict(HeatValvePoint(n=n, xi=xi)), + "cat_overlap": 0.9, + "cat_gap": gap, + } + ) + manifest = build_heat_valve_manifest( + {"complete": True, "points": records} + ) + assert not manifest["complete"] + assert [ + point["xi"] for point in manifest["selected_points"] + ] == [2.0, 2.2, 2.4] * 3 diff --git a/tracks/mps/solutions/Ranger-123/tests/test_heat_valve_audit.py b/tracks/mps/solutions/Ranger-123/tests/test_heat_valve_audit.py new file mode 100644 index 000000000..5f3fa24cd --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_heat_valve_audit.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from collections.abc import Callable +from typing import Any + +import pytest +from heat_valve_fixtures import valid_heat_valve_manifest + +from floquet_if_manybody.heat_valve_audit import audit_heat_valve_manifest + + +@pytest.mark.parametrize( + ("mutation", "failure"), + [ + ( + lambda manifest: manifest["points"][0]["model"].update( + drive_frequency=2.9 + ), + "fixed drive frequency", + ), + ( + lambda manifest: manifest["points"][1]["pole_fit"].update( + reconstruction_residual=0.08 + ), + "pole reconstruction", + ), + ( + lambda manifest: manifest["points"][1].update( + integrated_absolute_heat=0.2 + ), + "tenfold heat suppression", + ), + ( + lambda manifest: manifest["points"][1].update( + visible_residue_weight=0.2 + ), + "tenfold residue suppression", + ), + ( + lambda manifest: manifest["points"][1]["poles"][0].update( + eigenpair_residual=1e-6 + ), + "eigenpair residual", + ), + ( + lambda manifest: manifest["points"][1]["poles"][0][ + "eigenvalue" + ].update(abs=1.01), + "unit disk", + ), + ], +) +def test_audit_rejects_unsupported_dark_claim( + mutation: Callable[[dict[str, Any]], None], + failure: str, +) -> None: + manifest = valid_heat_valve_manifest() + mutation(manifest) + audit = audit_heat_valve_manifest(manifest) + assert not audit.dark_channel_passed + assert any(failure in item for item in audit.failures) + + +def test_audit_accepts_fixed_frequency_many_body_valve() -> None: + audit = audit_heat_valve_manifest(valid_heat_valve_manifest()) + assert audit.complete + assert audit.dark_channel_passed + assert audit.many_body_amplification_passed + assert audit.markov_payoff_passed + assert audit.failures == () + assert audit.metrics["heat_contrast_n3"] == 120.0 diff --git a/tracks/mps/solutions/Ranger-123/tests/test_influence.py b/tracks/mps/solutions/Ranger-123/tests/test_influence.py new file mode 100644 index 000000000..ce7704b51 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_influence.py @@ -0,0 +1,18 @@ +import numpy as np +from numpy.testing import assert_allclose + +from floquet_if_manybody.config import BathConfig +from floquet_if_manybody.influence import discretize_influence + + +def test_zero_coupling_has_zero_coefficients(): + coefficients = discretize_influence(BathConfig(alpha=0), 0.1, 3) + assert_allclose(coefficients.values, 0, atol=1e-15) + + +def test_small_cell_diagonal_limit(): + bath = BathConfig(alpha=0.05, cutoff=2.5) + dt = 1e-3 + coefficient = discretize_influence(bath, dt, 1).values[0] + assert_allclose(coefficient, 0.5 * bath.alpha * bath.cutoff**2 * dt**2, rtol=2e-3) + assert np.imag(coefficient) < 0 diff --git a/tracks/mps/solutions/Ranger-123/tests/test_model_comparison.py b/tracks/mps/solutions/Ranger-123/tests/test_model_comparison.py new file mode 100644 index 000000000..2cf27a2b7 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_model_comparison.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import numpy as np +from numpy.testing import assert_allclose + +from floquet_if_manybody.config import BathConfig +from floquet_if_manybody.model_comparison import ( + diagnostic_heat_rescaling, + model_variants, + variant_operators, +) + + +def test_variants_have_expected_coupling_norms() -> None: + variants = model_variants(n=3, j=0.5, bath=BathConfig(alpha=0.1, cutoff=2.5)) + bounded = next(item for item in variants if item.name == "bounded_no_ct") + kac = next(item for item in variants if item.name == "kac_no_ct") + _, bounded_s = variant_operators(bounded) + _, kac_s = variant_operators(kac) + assert_allclose(np.linalg.norm(bounded_s, 2), 1.0) + assert_allclose(np.linalg.norm(kac_s, 2), np.sqrt(3)) + + +def test_counterterm_difference_is_explicit_s_squared() -> None: + bath = BathConfig(alpha=0.1, cutoff=2.5) + variants = model_variants(n=3, j=0.5, bath=bath) + no_ct = next(item for item in variants if item.name == "bounded_no_ct") + with_ct = next(item for item in variants if item.name == "bounded_ct") + h0, coupling = variant_operators(no_ct) + hct, _ = variant_operators(with_ct) + assert_allclose(hct - h0, bath.alpha * bath.cutoff * coupling @ coupling) + assert with_ct.metadata["counterterm_strength"] == 0.25 + assert with_ct.metadata["normalization"] == "bounded" + + +def test_diagnostic_rescaling_never_overwrites_raw_heat() -> None: + values = np.array([1.0, 2.0]) + raw, diagnostic = diagnostic_heat_rescaling(values, eta=1 / 3) + assert_allclose(raw, values) + assert_allclose(diagnostic, 9 * values) + assert not np.shares_memory(raw, values) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_models.py b/tracks/mps/solutions/Ranger-123/tests/test_models.py new file mode 100644 index 000000000..cd1bb7675 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_models.py @@ -0,0 +1,46 @@ +from numpy.testing import assert_allclose + +from floquet_if_manybody.config import ModelConfig +from floquet_if_manybody.models import ( + coupling_operator, + drive_operator, + ising_hamiltonian, +) + + +def test_n3_ising_diagonal_is_open_boundary(): + cfg = ModelConfig(n=3, j=1, omega=1) + assert_allclose( + ising_hamiltonian(cfg).diagonal(), + [-2, 0, 2, 0, 0, 2, 0, -2], + ) + + +def test_counterterm_has_explicit_physical_coefficient(): + bare = ModelConfig(n=2, counterterm_strength=0) + dressed = ModelConfig(n=2, counterterm=True, counterterm_strength=0.125) + s = coupling_operator(bare) + assert_allclose( + ising_hamiltonian(dressed) - ising_hamiltonian(bare), + 0.125 * (s @ s), + ) + + +def test_per_spin_drive_is_independent_of_bath_normalization() -> None: + bounded = ModelConfig( + n=3, + normalization="bounded", + drive_normalization="per_spin", + ) + kac = ModelConfig( + n=3, + normalization="kac", + drive_normalization="per_spin", + ) + assert_allclose(drive_operator(bounded), drive_operator(kac)) + assert_allclose(drive_operator(bounded), 3 * coupling_operator(bounded)) + + +def test_default_drive_operator_preserves_existing_semantics() -> None: + config = ModelConfig(n=3, normalization="bounded") + assert_allclose(drive_operator(config), coupling_operator(config)) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_n2_heat.py b/tracks/mps/solutions/Ranger-123/tests/test_n2_heat.py new file mode 100644 index 000000000..1248c564f --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_n2_heat.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from numpy.testing import assert_allclose + +from floquet_if_manybody.n2_heat import N2HeatPoint, prepare_n2_triplet + + +def test_n2_triplet_preparation_tracks_exact_low_gap() -> None: + prepared = prepare_n2_triplet(N2HeatPoint(j=0.5)) + assert prepared.dimension == 3 + assert_allclose(prepared.bright_gap, (1.25) ** 0.5 - 0.5) + assert_allclose(prepared.model.drive_frequency, prepared.bright_gap) + + +def test_n2_drive_ratio_and_counterterm_are_explicit() -> None: + prepared = prepare_n2_triplet( + N2HeatPoint( + j=0.5, + drive_ratio=1.25, + alpha=0.1, + cutoff=2.5, + counterterm=True, + ) + ) + assert_allclose(prepared.model.drive_frequency, 1.25 * prepared.bright_gap) + assert prepared.model.counterterm_strength == 0.25 diff --git a/tracks/mps/solutions/Ranger-123/tests/test_n3_heat.py b/tracks/mps/solutions/Ranger-123/tests/test_n3_heat.py new file mode 100644 index 000000000..17bbc92a2 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_n3_heat.py @@ -0,0 +1,181 @@ +from __future__ import annotations + +import numpy as np +from numpy.testing import assert_allclose + +import floquet_if_manybody.n3_heat as n3_heat_module +from floquet_if_manybody.backends.uniform_tempo import UniformTempoResult +from floquet_if_manybody.correlations import CorrelationResult +from floquet_if_manybody.n3_heat import ( + N3HeatPoint, + compare_sector_spectra, + prepare_n3_sector, + run_n3_heat_point, +) + + +def test_odd_point_is_j_independent_before_backend() -> None: + a = prepare_n3_sector(N3HeatPoint(j=0.25, sector="odd")) + b = prepare_n3_sector(N3HeatPoint(j=1.0, sector="odd")) + assert_allclose(a.h0, b.h0, atol=1e-13) + assert_allclose(a.coupling, b.coupling, atol=1e-13) + assert a.dimension == 2 + + +def test_default_drive_tracks_sector_bright_gap() -> None: + even = prepare_n3_sector(N3HeatPoint(j=0.5, sector="even")) + odd = prepare_n3_sector(N3HeatPoint(j=0.5, sector="odd")) + assert_allclose(even.bright_gap, 0.4450418679126287, rtol=1e-10) + assert_allclose(even.model.drive_frequency, even.bright_gap) + assert_allclose(odd.bright_gap, 1.0, atol=1e-13) + assert_allclose(odd.model.drive_frequency, 1.0, atol=1e-13) + + +def test_n3_per_spin_drive_projection_differs_from_bounded_bath() -> None: + prepared = prepare_n3_sector( + N3HeatPoint( + j=0.5, + sector="even", + drive_normalization="per_spin", + ) + ) + assert_allclose(prepared.drive, 3 * prepared.coupling) + + +def test_derived_drive_frequency_is_stable_at_cache_precision() -> None: + prepared = prepare_n3_sector(N3HeatPoint(j=0.5, sector="even")) + assert prepared.bright_gap == 0.44504186791263 + + +def test_drive_ratio_and_counterterm_are_explicit() -> None: + prepared = prepare_n3_sector( + N3HeatPoint( + j=0.5, + sector="even", + drive_ratio=1.25, + alpha=0.1, + cutoff=2.5, + counterterm=True, + ) + ) + assert_allclose(prepared.model.drive_frequency, 1.25 * prepared.bright_gap) + assert prepared.model.counterterm_strength == 0.25 + + +def test_phase_sample_count_must_divide_period_steps() -> None: + with np.testing.assert_raises_regex(ValueError, "phase_samples"): + N3HeatPoint(steps_per_period=12, phase_samples=5) + + +def test_uniform_backend_controls_are_explicit() -> None: + point = N3HeatPoint( + backend="uniform_tempo", + steps_per_period=60, + phase_samples=3, + epsrel=1e-7, + uniform_auto_nc=False, + uniform_memory_cutoff=500, + uniform_low_rank_svd=True, + uniform_truncation="abs", + uniform_cap_rank=400, + uniform_max_rank=800, + ) + assert point.backend == "uniform_tempo" + assert point.uniform_memory_cutoff == 500 + assert point.uniform_truncation == "abs" + + +def test_backend_label_is_validated() -> None: + with np.testing.assert_raises_regex(ValueError, "backend"): + N3HeatPoint(backend="unknown") # type: ignore[arg-type] + + +def test_uniform_backend_routes_through_existing_heat_transform(monkeypatch) -> None: + class FakeUniformBackend: + def __init__(self, **kwargs) -> None: + assert kwargs["tensor_cache_directory"] is None + + def run_periodic( + self, + h0, + coupling, + model, + bath, + controls, + *, + drive_operator=None, + ): + assert h0.shape == (6, 6) + assert coupling.shape == (6, 6) + assert_allclose(drive_operator, coupling) + assert controls.steps_per_period == 4 + assert controls.phase_samples == 2 + delays = np.linspace(0.0, model.period, 5) + total = np.linspace(0.1, 0.01, 5).astype(complex) + correlation = CorrelationResult( + delays, + total, + total.copy(), + np.zeros(5), + (), + "uniform_tempo_floquet_multitime", + {"dt": model.period / 4}, + ) + return UniformTempoResult( + "uniform_tempo_floquet_multitime", + np.eye(6, dtype=complex) / 6, + np.repeat((np.eye(6, dtype=complex) / 6)[None, ...], 2, axis=0), + correlation, + { + "trace_error": 1e-8, + "hermiticity_error": 1e-8, + "minimum_density_eigenvalue": 1 / 6, + "fixed_point_residual": 1e-8, + "floquet_transfer_residual": 1e-8, + }, + { + "dt": model.period / 4, + "period_steps": 4, + "phase_samples": 2, + "bond_dimension": 3, + "tolerance": controls.tolerance, + "julia_version": "1.12.6", + "uniform_tempo_revision": "b76a018", + "manifest_sha256": "a" * 64, + }, + ) + + monkeypatch.setattr( + n3_heat_module, + "UniformTempoBackend", + FakeUniformBackend, + ) + result = run_n3_heat_point( + N3HeatPoint( + backend="uniform_tempo", + steps_per_period=4, + phase_samples=2, + delay_periods=1, + epsrel=1e-4, + frequency_points=9, + ), + commit="test", + ) + assert result["converged"] + assert result["method"] == "uniform_tempo_floquet_multitime" + assert result["diagnostics"]["bond_dimension"] == 3 + assert len(result["continuous"]) == 9 + + +def test_compare_sector_spectra_requires_matching_grids() -> None: + even = { + "frequency": [0.0, 1.0, 2.0], + "continuous": [0.0, 2.0, 0.0], + } + odd = { + "frequency": [0.0, 1.0, 2.0], + "continuous": [0.0, 1.0, 0.0], + } + comparison = compare_sector_spectra(even, odd) + assert comparison["maximum_absolute_difference"] == 1.0 + assert np.isclose(comparison["normalized_l1_difference"], 1.0) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_operators.py b/tracks/mps/solutions/Ranger-123/tests/test_operators.py new file mode 100644 index 000000000..fc3d90d3a --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_operators.py @@ -0,0 +1,17 @@ +import numpy as np +from numpy.testing import assert_allclose + +from floquet_if_manybody.operators import collective_operator, pauli, site_operator + + +def test_collective_z_eigenvalues(): + s = collective_operator("z", n=2, eta=0.5) + assert_allclose(np.diag(s), [1, 0, 0, -1]) + + +def test_site_algebra(): + x0 = site_operator("x", 0, 2) + y0 = site_operator("y", 0, 2) + z0 = site_operator("z", 0, 2) + assert_allclose(x0 @ y0, 1j * z0) + assert_allclose(pauli("x") @ pauli("x"), np.eye(2)) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_paper_audit.py b/tracks/mps/solutions/Ranger-123/tests/test_paper_audit.py new file mode 100644 index 000000000..3a7d74b08 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_paper_audit.py @@ -0,0 +1,176 @@ +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +from floquet_if_manybody.paper_extension import audit_paper_results + + +def _evidence() -> list[dict[str, Any]]: + common = { + "coarse_fingerprint": "a" * 64, + "refined_fingerprint": "b" * 64, + "state_residual": 1e-3, + "correlation_residual": 2e-3, + "heat_residual": 3e-3, + "passed": True, + "coarse_bond_dimension": 10, + "refined_bond_dimension": 12, + "coarse_phase_samples": 3, + "refined_phase_samples": 3, + } + return [ + { + **common, + "parameter": "epsrel", + "coarse_value": 3e-7, + "refined_value": 1e-7, + "coarse_steps_per_period": 60, + "refined_steps_per_period": 60, + "coarse_tolerance": 3e-7, + "refined_tolerance": 1e-7, + }, + { + **common, + "parameter": "epsrel", + "coarse_value": 3e-7, + "refined_value": 1e-7, + "coarse_steps_per_period": 90, + "refined_steps_per_period": 90, + "coarse_tolerance": 3e-7, + "refined_tolerance": 1e-7, + }, + { + **common, + "parameter": "steps_per_period", + "coarse_value": 60, + "refined_value": 90, + "coarse_steps_per_period": 60, + "refined_steps_per_period": 90, + "coarse_tolerance": 1e-7, + "refined_tolerance": 1e-7, + }, + { + **common, + "parameter": "phase_samples", + "coarse_value": 3, + "refined_value": 15, + "coarse_steps_per_period": 90, + "refined_steps_per_period": 90, + "coarse_tolerance": 1e-7, + "refined_tolerance": 1e-7, + "refined_phase_samples": 15, + }, + ] + + +def _diagnostics() -> dict[str, float]: + return { + "fixed_point_residual": 1e-5, + "trace_error": 1e-5, + "hermiticity_error": 1e-5, + "connected_tail_amplitude": 1e-3, + "minimum_density_eigenvalue": 0.0, + } + + +def _write_manifests(directory: Path) -> None: + n3_points = [ + { + "sector": sector, + "model": {"j": j}, + "adaptive_status": "converged", + "adaptive_converged": True, + "diagnostics": _diagnostics(), + "evidence": _evidence(), + } + for sector in ("even", "odd") + for j in (0.25, 0.5, 1.0) + ] + error_points = [ + { + "alpha": alpha, + "drive_ratio": ratio, + "status": "converged", + "metrics": { + "trace_distance": 0.1, + "correlation": 0.2, + "heat": 0.3, + }, + "convergence_evidence": _evidence(), + } + for alpha in (0.025, 0.05, 0.1) + for ratio in (0.75, 1.0, 1.25) + ] + model_points = [ + { + "variant": variant, + "adaptive_status": "converged", + "adaptive_converged": True, + "diagnostics": _diagnostics(), + "evidence": _evidence(), + } + for variant in ("bounded", "bounded_ct", "kac", "kac_ct") + ] + payloads = { + "n3_heat_manifest.json": { + "exact_backend": "uniform_tempo", + "converged": True, + "points": n3_points, + "odd_cross_j_relative_max_difference": 0.0, + }, + "error_map_manifest.json": { + "exact_backend": "uniform_tempo", + "converged": True, + "points": error_points, + }, + "model_comparison_manifest.json": { + "exact_backend": "uniform_tempo", + "complete": True, + "locally_complete": True, + "converged": True, + "points": model_points, + }, + } + for name, payload in payloads.items(): + (directory / name).write_text(json.dumps(payload), encoding="utf-8") + + +def test_paper_audit_requires_nested_convergence_and_all_points( + tmp_path: Path, +) -> None: + _write_manifests(tmp_path) + passed, failures = audit_paper_results(tmp_path) + assert passed + assert failures == [] + + path = tmp_path / "n3_heat_manifest.json" + payload = json.loads(path.read_text(encoding="utf-8")) + payload["points"][0]["evidence"] = payload["points"][0]["evidence"][1:] + path.write_text(json.dumps(payload), encoding="utf-8") + passed, failures = audit_paper_results(tmp_path) + assert not passed + assert any("both timestep grids" in failure for failure in failures) + + +def test_paper_audit_accepts_explicit_kac_timestep_resource_ceiling( + tmp_path: Path, +) -> None: + _write_manifests(tmp_path) + path = tmp_path / "model_comparison_manifest.json" + payload = json.loads(path.read_text(encoding="utf-8")) + payload["converged"] = False + for point in payload["points"]: + if not point["variant"].startswith("kac_"): + continue + point["adaptive_status"] = "resource_ceiling" + point["adaptive_converged"] = False + point["failed_parameter"] = "steps_per_period" + point["evidence"] = [ + item for item in point["evidence"] if item["parameter"] == "epsrel" + ][:1] + path.write_text(json.dumps(payload), encoding="utf-8") + passed, failures = audit_paper_results(tmp_path) + assert passed + assert failures == [] diff --git a/tracks/mps/solutions/Ranger-123/tests/test_paper_extension.py b/tracks/mps/solutions/Ranger-123/tests/test_paper_extension.py new file mode 100644 index 000000000..2824138d1 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_paper_extension.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from copy import deepcopy + +from floquet_if_manybody.n3_heat import N3HeatPoint, prepare_n3_sector +from floquet_if_manybody.paper_extension import ( + _odd_equivalent_payload, + n2_correlation_delay_periods, + uniform_error_schedule, + uniform_publication_schedule, +) + + +def test_odd_equivalent_payload_reuses_only_the_projected_model() -> None: + point = N3HeatPoint(j=0.25, sector="odd", backend="uniform_tempo") + prepared = prepare_n3_sector(point) + reference = { + "fingerprint": "a" * 64, + "source_commit": "test", + "point": {"j": 0.25}, + "final_point": { + **point.__dict__, + "steps_per_period": 60, + "phase_samples": 3, + }, + "model": { + "n": 3, + "j": 0.25, + "drive_frequency": prepared.model.drive_frequency, + }, + "model_hash": "b" * 64, + "projected_model_hash": "c" * 64, + "bright_gap": prepared.bright_gap, + "continuous": [0.0, 1.0], + "adaptive_converged": True, + } + untouched = deepcopy(reference) + target = _odd_equivalent_payload(reference, 1.0) + assert reference == untouched + assert target["point"]["j"] == 1.0 + assert target["final_point"]["j"] == 1.0 + assert target["model"]["j"] == 1.0 + assert target["continuous"] == reference["continuous"] + assert target["projected_model_hash"] == reference["projected_model_hash"] + assert target["numerical_reuse"]["h0_frobenius_residual"] < 1e-13 + assert target["numerical_reuse"]["coupling_frobenius_residual"] < 1e-13 + + +def test_n2_correlation_window_grows_at_weak_coupling() -> None: + assert n2_correlation_delay_periods(0.1) == 4 + assert n2_correlation_delay_periods(0.05) == 6 + assert n2_correlation_delay_periods(0.025) == 12 + + +def test_n2_error_grid_has_a_deeper_compression_ladder() -> None: + publication = uniform_publication_schedule() + error = uniform_error_schedule() + assert error.steps_per_period == publication.steps_per_period + assert error.phase_samples == publication.phase_samples + assert error.tolerances[: len(publication.tolerances)] == publication.tolerances + assert error.tolerances[-1] < publication.tolerances[-1] diff --git a/tracks/mps/solutions/Ranger-123/tests/test_paper_plotting.py b/tracks/mps/solutions/Ranger-123/tests/test_paper_plotting.py new file mode 100644 index 000000000..72e1e1cea --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_paper_plotting.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +from pathlib import Path + +from heat_valve_fixtures import valid_heat_valve_manifest + +from floquet_if_manybody.plotting import ( + plot_dark_diagnostics, + plot_error_maps, + plot_heat_valve_hero, + plot_model_variants, + plot_n3_sector_heat, + plot_odd_sector_difference, +) + + +def _n3_manifest() -> dict[str, object]: + points = [] + diagnostics = [] + for sector in ("even", "odd"): + for j in (0.25, 0.5, 1.0): + points.append( + { + "sector": sector, + "adaptive_converged": True, + "model": {"j": j}, + "frequency": [0.0, 1.0, 2.0], + "continuous": [0.0, j, 0.0], + } + ) + diagnostics.append( + { + "sector": sector, + "j": j, + "integrated_continuous_heat": j, + "period_variance": 0.5, + "strongest_transitions": [ + {"source": 0, "target": 1, "weight": j} + ], + } + ) + return {"points": points, "dark_diagnostics": diagnostics} + + +def test_all_paper_plots_render_pdf_and_png(tmp_path: Path) -> None: + n3 = _n3_manifest() + plot_n3_sector_heat(n3, tmp_path / "sector") + plot_odd_sector_difference(n3, tmp_path / "odd") + plot_dark_diagnostics(n3, tmp_path / "dark") + error = { + "points": [ + { + "alpha": alpha, + "drive_ratio": ratio, + "status": "converged", + "metrics": { + "trace_distance": 0.1, + "correlation": 0.2, + "heat": 0.3, + }, + } + for alpha in (0.025, 0.05, 0.1) + for ratio in (0.75, 1.0, 1.25) + ] + } + plot_error_maps(error, tmp_path / "errors") + models = { + "points": [ + { + "adaptive_converged": True, + "variant": name, + "frequency": [0.0, 1.0], + "continuous": [0.0, 1.0], + "continuous_eta_rescaled": [0.0, 2.0], + } + for name in ("bounded_no_ct", "bounded_ct", "kac_no_ct", "kac_ct") + ] + } + plot_model_variants(models, tmp_path / "models") + assert len(list(tmp_path.glob("*.pdf"))) == 5 + assert len(list(tmp_path.glob("*.png"))) == 5 + + +def test_heat_valve_hero_writes_png_and_pdf(tmp_path: Path) -> None: + plot_heat_valve_hero( + valid_heat_valve_manifest(), + tmp_path / "heat_valve_hero", + ) + assert (tmp_path / "heat_valve_hero.png").is_file() + assert (tmp_path / "heat_valve_hero.pdf").is_file() diff --git a/tracks/mps/solutions/Ranger-123/tests/test_poles.py b/tracks/mps/solutions/Ranger-123/tests/test_poles.py new file mode 100644 index 000000000..5470d5700 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_poles.py @@ -0,0 +1,111 @@ +from __future__ import annotations + +import numpy as np +import pytest +from numpy.testing import assert_allclose + +from floquet_if_manybody.poles import ( + TransferPole, + fit_pole_residues, + match_transfer_poles, + transfer_poles, +) + + +def _poles(values: list[complex]) -> tuple[TransferPole, ...]: + return tuple( + TransferPole( + eigenvalue=value, + decay_rate=float(-np.log(abs(value))), + quasifrequency=float(np.angle(value)), + eigenpair_residual=1e-13, + ) + for value in values + ) + + +def test_transfer_poles_convert_eigenvalues_to_rates() -> None: + period = 2.0 + values = np.array([1.0, np.exp((-0.2 + 0.7j) * period)]) + poles = transfer_poles(values, np.array([1e-13, 2e-13]), period) + assert len(poles) == 1 + assert_allclose(poles[0].decay_rate, 0.2, atol=1e-12) + assert_allclose(poles[0].quasifrequency, 0.7, atol=1e-12) + + +def test_transfer_poles_accept_resolved_approximate_steady_mode() -> None: + poles = transfer_poles( + np.array([0.9991 + 1e-15j, 0.8 + 0.1j]), + np.array([1e-13, 1e-13]), + period=1.0, + ) + assert len(poles) == 1 + assert poles[0].eigenvalue == 0.8 + 0.1j + + +def test_fit_recovers_complex_residues() -> None: + period = 1.5 + eigenvalues = np.array([0.8 * np.exp(0.2j), 0.55 * np.exp(-0.4j)]) + residues = np.array([0.7 - 0.1j, -0.2 + 0.3j]) + n = np.arange(16) + connected = sum( + residue * eigenvalue**n + for residue, eigenvalue in zip(residues, eigenvalues, strict=True) + ) + fit = fit_pole_residues( + transfer_poles( + np.r_[1.0, eigenvalues], + np.full(3, 1e-13), + period, + ), + n * period, + connected, + period, + max_modes=2, + ) + assert_allclose( + [item.residue for item in fit.residues], + residues, + atol=1e-10, + ) + assert fit.reconstruction_residual < 1e-12 + + +def test_fit_uses_only_stroboscopic_delays() -> None: + poles = _poles([0.8 + 0.1j]) + delays = np.arange(9) * 0.5 + connected = np.full(9, 99 + 0j) + connected[::2] = (0.3 - 0.2j) * poles[0].eigenvalue ** np.arange(5) + fit = fit_pole_residues( + poles, + delays, + connected, + period=1.0, + max_modes=1, + ) + assert_allclose(fit.residues[0].residue, 0.3 - 0.2j) + assert_allclose(fit.stroboscopic_delays, delays[::2]) + + +def test_fit_requires_an_overdetermined_stroboscopic_window() -> None: + with pytest.raises(ValueError, match="stroboscopic samples"): + fit_pole_residues( + _poles([0.8, 0.7]), + np.array([0.0, 1.0]), + np.array([1.0, 0.5], dtype=complex), + period=1.0, + max_modes=2, + ) + + +def test_mode_matching_follows_nearest_complex_poles() -> None: + previous = _poles([0.9 + 0.1j, 0.7 - 0.2j]) + current = _poles([0.69 - 0.19j, 0.89 + 0.11j]) + matched = match_transfer_poles(previous, current) + assert [item.current_index for item in matched] == [1, 0] + + +def test_degenerate_matching_is_marked_ambiguous() -> None: + previous = _poles([0.8 + 0.1j, 0.8 + 0.1j]) + current = _poles([0.8 + 0.1j, 0.8 + 0.1j]) + assert all(item.ambiguous for item in match_transfer_poles(previous, current)) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_pt_tempo.py b/tracks/mps/solutions/Ranger-123/tests/test_pt_tempo.py new file mode 100644 index 000000000..4b5773760 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_pt_tempo.py @@ -0,0 +1,30 @@ +import numpy as np +import pytest + +from floquet_if_manybody.backends.pt_tempo import PtTempoBackend +from floquet_if_manybody.config import BathConfig + +oqupy = pytest.importorskip("oqupy") +if np.lib.NumpyVersion(np.__version__) >= "2.0.0": + pytest.skip("OQuPy 0.5 requires NumPy <2", allow_module_level=True) + + +def test_pt_tempo_short_run_is_labeled_and_trace_preserving(): + h = np.array([[0, 0.5], [0.5, 0]], dtype=complex) + s = np.diag([1, -1]).astype(complex) + rho = np.array([[1, 0], [0, 0]], dtype=complex) + run = PtTempoBackend().run( + lambda _time: h, + s, + rho, + BathConfig(alpha=0.01), + dt=0.1, + steps=6, + memory_steps=2, + epsrel=1e-5, + ) + assert run.result.method == "pt_tempo" + assert run.result.diagnostics["trace_error"] < 1e-3 + assert run.result.metadata["spectral_density_convention"].startswith( + "OQuPy alpha divided" + ) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_spectra_n2.py b/tracks/mps/solutions/Ranger-123/tests/test_spectra_n2.py new file mode 100644 index 000000000..e0afc072f --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_spectra_n2.py @@ -0,0 +1,31 @@ +import numpy as np +from numpy.testing import assert_allclose + +from floquet_if_manybody.config import ModelConfig +from floquet_if_manybody.models import coupling_operator, ising_hamiltonian +from floquet_if_manybody.spectra import diagonalize, transitions +from floquet_if_manybody.symmetry import n2_sectors, project + + +def test_n2_analytic_triplet_spectrum_and_weights(): + _, triplet = n2_sectors() + omega = 1.0 + for j in [0.0, 0.25, 0.5, 1.0, 2.0]: + cfg = ModelConfig(n=2, j=j, omega=omega) + spectrum = diagonalize(project(ising_hamiltonian(cfg), triplet)) + e = np.sqrt(j**2 + omega**2) + assert_allclose(spectrum.energies, [-e, -j, e], atol=1e-12) + items = transitions( + spectrum, spectrum, project(coupling_operator(cfg), triplet), threshold=1e-10 + ) + from_ground = sorted( + [x for x in items if x.source == 0 and x.frequency > 0], + key=lambda x: x.frequency, + ) + assert_allclose([x.frequency for x in from_ground], [e - j], atol=1e-12) + expected_low = 2 * cfg.eta**2 * (1 + j / e) + assert_allclose(from_ground[0].weight, expected_low, atol=1e-12) + high = [x for x in items if x.source == 1 and x.target == 2] + expected_high = 2 * cfg.eta**2 * (1 - j / e) + assert_allclose(high[0].frequency, e + j, atol=1e-12) + assert_allclose(high[0].weight, expected_high, atol=1e-12) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_spectra_n3.py b/tracks/mps/solutions/Ranger-123/tests/test_spectra_n3.py new file mode 100644 index 000000000..4f57a89e4 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_spectra_n3.py @@ -0,0 +1,29 @@ +import numpy as np +from numpy.testing import assert_allclose + +from floquet_if_manybody.config import ModelConfig +from floquet_if_manybody.models import ising_hamiltonian +from floquet_if_manybody.spectra import diagonalize +from floquet_if_manybody.symmetry import n3_reflection_sectors, project + + +def test_n3_odd_gap_is_single_spin_gap(): + odd, _ = n3_reflection_sectors() + for j in [0, 0.5, 2.0]: + cfg = ModelConfig(n=3, j=j, omega=1) + energies = diagonalize(project(ising_hamiltonian(cfg), odd)).energies + assert_allclose(np.diff(energies), [1.0], atol=1e-12) + + +def test_n3_cat_gap_asymptotic_coefficient(): + _, even = n3_reflection_sectors() + ratios = [] + for j in [4.0, 8.0, 16.0]: + cfg = ModelConfig(n=3, j=j, omega=1) + se = diagonalize(project(ising_hamiltonian(cfg), even)) + # The cat partner has opposite global spin-flip parity inside the even + # reflection sector, so use the two lowest levels of this block. + gap = se.energies[1] - se.energies[0] + ratios.append(gap * 4 * j**2) + assert abs(ratios[-1] - 1) < abs(ratios[0] - 1) + np.testing.assert_allclose(ratios[-1], 1.0, rtol=0.02) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_symmetry_n2.py b/tracks/mps/solutions/Ranger-123/tests/test_symmetry_n2.py new file mode 100644 index 000000000..65464d579 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_symmetry_n2.py @@ -0,0 +1,14 @@ +from numpy.testing import assert_allclose + +from floquet_if_manybody.config import ModelConfig +from floquet_if_manybody.models import coupling_operator, ising_hamiltonian +from floquet_if_manybody.symmetry import n2_sectors, project, sector_residual + + +def test_n2_singlet_is_dark_and_invariant(): + singlet, triplet = n2_sectors() + cfg = ModelConfig(n=2) + assert (singlet.dimension, triplet.dimension) == (1, 3) + assert_allclose(project(coupling_operator(cfg), singlet), 0, atol=1e-14) + assert sector_residual(ising_hamiltonian(cfg), singlet) < 1e-13 + assert sector_residual(ising_hamiltonian(cfg), triplet) < 1e-13 diff --git a/tracks/mps/solutions/Ranger-123/tests/test_symmetry_n3.py b/tracks/mps/solutions/Ranger-123/tests/test_symmetry_n3.py new file mode 100644 index 000000000..5d91d288b --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_symmetry_n3.py @@ -0,0 +1,15 @@ +from numpy.testing import assert_allclose + +from floquet_if_manybody.config import ModelConfig +from floquet_if_manybody.models import ising_hamiltonian +from floquet_if_manybody.symmetry import n3_reflection_sectors, project, sector_residual + + +def test_n3_odd_sector_is_j_independent(): + odd, even = n3_reflection_sectors() + h1 = project(ising_hamiltonian(ModelConfig(n=3, j=0.2)), odd) + h2 = project(ising_hamiltonian(ModelConfig(n=3, j=2.0)), odd) + assert_allclose(h1, h2) + assert (odd.dimension, even.dimension) == (2, 6) + assert sector_residual(ising_hamiltonian(ModelConfig(n=3)), odd) < 1e-13 + assert sector_residual(ising_hamiltonian(ModelConfig(n=3)), even) < 1e-13 diff --git a/tracks/mps/solutions/Ranger-123/tests/test_symmetry_n4.py b/tracks/mps/solutions/Ranger-123/tests/test_symmetry_n4.py new file mode 100644 index 000000000..937599ffb --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_symmetry_n4.py @@ -0,0 +1,21 @@ +import numpy as np + +from floquet_if_manybody.config import ModelConfig +from floquet_if_manybody.models import coupling_operator, ising_hamiltonian +from floquet_if_manybody.symmetry import n4_reflection_sectors, sector_residual + + +def test_n4_reflection_dimensions_and_invariance() -> None: + odd, even = n4_reflection_sectors() + assert odd.dimension == 6 + assert even.dimension == 10 + model = ModelConfig(n=4, j=0.5) + h0 = ising_hamiltonian(model) + coupling = coupling_operator(model) + for sector in (odd, even): + assert sector_residual(h0, sector) < 1e-13 + assert sector_residual(coupling, sector) < 1e-13 + assert np.allclose( + sector.isometry.conj().T @ sector.isometry, + np.eye(sector.dimension), + ) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_uniform_tempo_backend.py b/tracks/mps/solutions/Ranger-123/tests/test_uniform_tempo_backend.py new file mode 100644 index 000000000..d5dd9ffb2 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_uniform_tempo_backend.py @@ -0,0 +1,284 @@ +from __future__ import annotations + +import json +import sys +from pathlib import Path + +import numpy as np +import pytest +from numpy.testing import assert_allclose + +from floquet_if_manybody.backends.uniform_tempo import ( + UniformTempoBackend, + UniformTempoControls, + _encode_complex, + _tensor_cache_key, +) +from floquet_if_manybody.config import BathConfig, ModelConfig + + +def _write_fake_runner(path: Path, payload: dict[str, object], exit_code: int = 0) -> None: + script = f"""\ +import json +import pathlib +import sys + +if {exit_code}: + print("synthetic Julia failure", file=sys.stderr) + raise SystemExit({exit_code}) +input_path, output_path = sys.argv[1:3] +request = json.loads(pathlib.Path(input_path).read_text()) +pathlib.Path({str(Path("captured-request.json"))!r}).write_text(json.dumps(request)) +payload = {payload!r} +payload["request_echo"] = request +pathlib.Path(output_path).write_text(json.dumps(payload)) +""" + script = script.replace( + repr(str(Path("captured-request.json"))), + repr(str(path.parent / "captured-request.json")), + ) + path.write_text(script, encoding="utf-8") + + +def _valid_payload() -> dict[str, object]: + return { + "method": "uniform_tempo_floquet_multitime", + "dt": np.pi / 2, + "period_steps": 4, + "bond_dimension": 3, + "floquet_state": { + "real": [0.6, 0.0, 0.0, 0.4], + "imag": [0.0, 0.1, -0.1, 0.0], + "shape": [2, 2], + }, + "phase_states": { + "real": [0.6, 0.0, 0.0, 0.4, 0.4, 0.0, 0.0, 0.6], + "imag": [0.0] * 8, + "shape": [2, 2, 2], + }, + "one_point": [0.2, -0.2], + "phase_offsets": [0, 2], + "delay": [0.0, np.pi / 2, np.pi, 3 * np.pi / 2, 2 * np.pi], + "correlation": { + "real": [1.0, 0.5, 0.25, 0.125, 0.0625], + "imag": [0.0, -0.1, -0.05, -0.025, -0.0125], + "shape": [5], + }, + "diagnostics": { + "trace_error": 1e-8, + "hermiticity_error": 1e-8, + "minimum_density_eigenvalue": 0.3, + "fixed_point_residual": 1e-7, + "floquet_transfer_residual": 1e-7, + }, + "julia_version": "1.12.6", + "uniform_tempo_revision": "b76a018c32e5415989761d902b1b0e95f1a337da", + "manifest_sha256": "a" * 64, + "process_tensor_cache_hit": False, + "transfer_eigenvalues": { + "real": [], + "imag": [], + "shape": [0], + }, + "transfer_eigenpair_residuals": [], + "transfer_dimension": 12, + } + + +def _backend(tmp_path: Path, payload: dict[str, object]) -> UniformTempoBackend: + runner = tmp_path / "fake_runner.py" + _write_fake_runner(runner, payload) + return UniformTempoBackend(command_prefix=(sys.executable, str(runner))) + + +def _model() -> ModelConfig: + return ModelConfig( + n=3, + j=0.5, + drive_amplitude=0.2, + drive_frequency=1.0, + ) + + +def test_valid_payload_round_trips_complex_arrays(tmp_path: Path) -> None: + backend = _backend(tmp_path, _valid_payload()) + result = backend.run_periodic( + np.array([[0.0, 0.5], [0.5, 0.0]], dtype=complex), + np.diag([1.0, -1.0]).astype(complex), + _model(), + BathConfig(alpha=0.01, cutoff=2.5), + UniformTempoControls( + steps_per_period=4, + tolerance=1e-4, + phase_samples=2, + delay_periods=1, + auto_nc=False, + memory_cutoff=6, + ), + ) + assert result.method == "uniform_tempo_floquet_multitime" + assert result.phase_states.shape == (2, 2, 2) + assert_allclose(result.floquet_state, [[0.6, -0.1j], [0.1j, 0.4]]) + assert_allclose( + result.correlation.total, + [ + 1.0, + 0.5 - 0.1j, + 0.25 - 0.05j, + 0.125 - 0.025j, + 0.0625 - 0.0125j, + ], + ) + assert_allclose( + result.correlation.total, + result.correlation.connected + result.correlation.coherent, + ) + assert result.metadata["uniform_tempo_revision"].startswith("b76a018") + assert result.transfer_eigenvalues.size == 0 + + +def test_backend_round_trips_transfer_poles_and_independent_drive( + tmp_path: Path, +) -> None: + payload = _valid_payload() + payload["transfer_eigenvalues"] = { + "real": [1.0, 0.8], + "imag": [0.0, 0.1], + "shape": [2], + } + payload["transfer_eigenpair_residuals"] = [1e-12, 2e-11] + backend = _backend(tmp_path, payload) + drive = 2 * np.diag([1.0, -1.0]).astype(complex) + result = backend.run_periodic( + np.array([[0.0, 0.5], [0.5, 0.0]], dtype=complex), + np.diag([1.0, -1.0]).astype(complex), + _model(), + BathConfig(alpha=0.01), + UniformTempoControls(4, 1e-4, 2, 1, pole_count=2), + drive_operator=drive, + ) + assert_allclose(result.transfer_eigenvalues, [1.0, 0.8 + 0.1j]) + assert_allclose(result.transfer_eigenpair_residuals, [1e-12, 2e-11]) + request = json.loads((tmp_path / "captured-request.json").read_text()) + assert request["drive"]["real"] == _encode_complex(drive)["real"] + assert request["controls"]["pole_count"] == 2 + + +@pytest.mark.parametrize( + ("field", "value", "message"), + [ + ("transfer_eigenpair_residuals", [1e-12], "same length"), + ("transfer_eigenpair_residuals", [1e-12, -1.0], "nonnegative"), + ( + "transfer_eigenvalues", + {"real": [1.0, "nan"], "imag": [0.0, 0.0], "shape": [2]}, + "non-finite", + ), + ], +) +def test_invalid_transfer_pole_payload_is_rejected( + tmp_path: Path, + field: str, + value: object, + message: str, +) -> None: + payload = _valid_payload() + payload["transfer_eigenvalues"] = { + "real": [1.0, 0.8], + "imag": [0.0, 0.1], + "shape": [2], + } + payload["transfer_eigenpair_residuals"] = [1e-12, 2e-11] + payload[field] = value + backend = _backend(tmp_path, payload) + with pytest.raises(ValueError, match=message): + backend.run_periodic( + np.eye(2, dtype=complex), + np.diag([1.0, -1.0]).astype(complex), + _model(), + BathConfig(alpha=0.01), + UniformTempoControls(4, 1e-4, 2, 1, pole_count=2), + ) + + +def test_nonzero_runner_exit_is_reported(tmp_path: Path) -> None: + runner = tmp_path / "failing.py" + _write_fake_runner(runner, {}, exit_code=7) + backend = UniformTempoBackend(command_prefix=(sys.executable, str(runner))) + with pytest.raises(RuntimeError, match="synthetic Julia failure"): + backend.run_periodic( + np.eye(2, dtype=complex), + np.diag([1.0, -1.0]).astype(complex), + _model(), + BathConfig(alpha=0.01), + UniformTempoControls(4, 1e-4, 2, 1), + ) + + +@pytest.mark.parametrize( + ("mutation", "message"), + [ + (lambda payload: payload.pop("uniform_tempo_revision"), "provenance"), + (lambda payload: payload.update(method="wrong"), "method"), + ( + lambda payload: payload["correlation"].update(real=[1.0, 0.5]), + "shape", + ), + ], +) +def test_malformed_payload_is_rejected( + tmp_path: Path, mutation, message: str +) -> None: + payload = _valid_payload() + mutation(payload) + backend = _backend(tmp_path, payload) + with pytest.raises(ValueError, match=message): + backend.run_periodic( + np.eye(2, dtype=complex), + np.diag([1.0, -1.0]).astype(complex), + _model(), + BathConfig(alpha=0.01), + UniformTempoControls(4, 1e-4, 2, 1), + ) + + +def test_controls_require_commensurate_phase_sampling() -> None: + with pytest.raises(ValueError, match="phase_samples"): + UniformTempoControls(steps_per_period=6, tolerance=1e-5, phase_samples=4, delay_periods=1) + + +def test_process_tensor_key_excludes_phase_and_delay_controls() -> None: + coupling = np.diag([1.0, -1.0]).astype(complex) + model = _model() + bath = BathConfig(alpha=0.01, cutoff=2.5) + coarse = UniformTempoControls(60, 3e-7, 3, 2) + denser_phase = UniformTempoControls(60, 3e-7, 15, 8) + assert _tensor_cache_key(coupling, model, bath, coarse) == _tensor_cache_key( + coupling, + model, + bath, + denser_phase, + ) + + +def test_backend_passes_content_addressed_tensor_cache(tmp_path: Path) -> None: + runner = tmp_path / "fake_runner.py" + _write_fake_runner(runner, _valid_payload()) + backend = UniformTempoBackend( + command_prefix=(sys.executable, str(runner)), + tensor_cache_directory=tmp_path / "tensor-cache", + ) + controls = UniformTempoControls(4, 1e-4, 2, 1) + backend.run_periodic( + np.array([[0.0, 0.5], [0.5, 0.0]], dtype=complex), + np.diag([1.0, -1.0]).astype(complex), + _model(), + BathConfig(alpha=0.01, cutoff=2.5), + controls, + ) + request = json.loads((tmp_path / "captured-request.json").read_text()) + cache_key = request["controls"]["process_tensor_cache_key"] + assert len(cache_key) == 64 + assert request["controls"]["process_tensor_cache_path"].endswith( + f"{cache_key}.jls" + ) diff --git a/tracks/mps/solutions/Ranger-123/tests/test_uniform_tempo_environment.py b/tracks/mps/solutions/Ranger-123/tests/test_uniform_tempo_environment.py new file mode 100644 index 000000000..c5054ad64 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/tests/test_uniform_tempo_environment.py @@ -0,0 +1,37 @@ +import shutil +import subprocess +import tomllib +from pathlib import Path + +import pytest + +PROJECT_ROOT = Path(__file__).resolve().parents[1] +JULIA_PROJECT = PROJECT_ROOT / "julia" / "Project.toml" + + +def test_julia_project_declares_required_dependencies() -> None: + project = tomllib.loads(JULIA_PROJECT.read_text(encoding="utf-8")) + assert {"UniformTEMPO", "OrdinaryDiffEq", "JSON3", "KrylovKit"} <= project[ + "deps" + ].keys() + assert project["compat"]["julia"] == "1.12" + + +def test_julia_runner_rejects_missing_input(tmp_path: Path) -> None: + julia = shutil.which("julia") + if julia is None: + pytest.skip("Julia is not installed") + completed = subprocess.run( + [ + julia, + f"--project={PROJECT_ROOT / 'julia'}", + str(PROJECT_ROOT / "julia" / "run_uniform_tempo.jl"), + str(tmp_path / "missing.json"), + str(tmp_path / "out.json"), + ], + capture_output=True, + text=True, + check=False, + ) + assert completed.returncode != 0 + assert "input" in completed.stderr.lower() diff --git a/tracks/mps/solutions/Ranger-123/uv.lock b/tracks/mps/solutions/Ranger-123/uv.lock new file mode 100644 index 000000000..adfea015e --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/uv.lock @@ -0,0 +1,1013 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.12' and python_full_version < '3.15'", + "python_full_version < '3.12'", +] + +[[package]] +name = "ast-serialize" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/ad/0d70a3a2d6e01968d985415259e8ec7ad3f777903f9b1c1f3c8c44642c60/ast_serialize-0.6.0.tar.gz", hash = "sha256:aadd3ffcf4858c9726bf3515f7b199c7eadbe504f96028e4a87172c0da65a8fe", size = 61489, upload-time = "2026-06-30T20:02:55.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/12/3e5f575f156555547c250a8b0d1347517a3a20fc7f4492e9703a69d4f45e/ast_serialize-0.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a7520b672827885bafeae7501f684d14d47d17e5f45256f9df547686cca52264", size = 1177640, upload-time = "2026-06-30T20:02:06.708Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a4/921a9e27951627983b0f368859ea00f8330a551dc0bf4c2fdcb11855a98b/ast_serialize-0.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a14191beec7e0c078d2fc1f6edc0aee88bcd4db9f18e1bc9f8052b559c22dddc", size = 1168111, upload-time = "2026-06-30T20:02:08.366Z" }, + { url = "https://files.pythonhosted.org/packages/00/69/950cf404de7b8782cf95e5c1237e25e2aa46177b287f39f9eeddf481fd6f/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32ef62ec34cf6be20ad77d4799556638fbdf187f3ae10698dfb20ef9f2c89516", size = 1227656, upload-time = "2026-06-30T20:02:09.843Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a8/46f8f6a6479d9d2273980957bb091a506c55f5b95d3c029ee58518a78407/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13b7769970a39983b0adf2f38917b1cd3b8946f76df045756c3d741bc689f089", size = 1227706, upload-time = "2026-06-30T20:02:11.367Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/9ac415bda0a40e49eab8fea3b2741c19c98bb84d57d62c4cfc6230eb67be/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f7a408601bb3edaefb3bc67a4c01f5235e3253653b6a5729a2ee2382b35341c", size = 1431705, upload-time = "2026-06-30T20:02:12.737Z" }, + { url = "https://files.pythonhosted.org/packages/e5/06/8807115d441444879f7561b5eede5ac18fc80392f11826d61ccf31f503b1/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8670bfa51208a2c0c8d138928e40e998fab158f9200d53bb80c088b5b8eda7b8", size = 1249533, upload-time = "2026-06-30T20:02:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c0/c2ba82ef9618650357d9421a1fdb27ffec862a7f57e8e2de82a3ccd11e12/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4826809eb8597a8cd59fd924b6d7c285b8969a1e0007e2cb652cab62376270f", size = 1252619, upload-time = "2026-06-30T20:02:16.219Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a7/fa31d52dd4102cede29fb9634e98d214129b2783b4f95528c6dc6a8f6587/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:577a6c189068686869f5f1ddc38363f3ae1808a4753b577266f9202071a7bb66", size = 1242983, upload-time = "2026-06-30T20:02:17.813Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/ddf742b5ad3c4bafd3466f2265037cfd99bc1b9a5ee46a5d58c90d523242/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085de7f62dc9cc247eb01e965a362707d1d90b1d89a82c5bf78301a60a3c417b", size = 1296148, upload-time = "2026-06-30T20:02:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/24/cb/9f6f217cce8b3b632c5568b478d195a35e79dce4dbe309438cb89ba6ea4f/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9f8a8b78b13173de6a9ec22111d9be674874cd5bdccda04f14ae5ebc2bef403a", size = 1403826, upload-time = "2026-06-30T20:02:20.696Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f8/9d16d4f0107a183924425cc0e7618d8bf76f96b45afa9ff19f924ed1ad57/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f2ff3baffc3a29c1f15bc9098aa0c09763410262d5e6cef42116f7356c184554", size = 1502943, upload-time = "2026-06-30T20:02:22.034Z" }, + { url = "https://files.pythonhosted.org/packages/80/dd/bbc1c38756350dddf7e24acae1c9482ef42051c267417e019aecc1ed4075/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0067b25fce104eaae5b88383de9ab803faeb671831e14ca698b771b356e2600f", size = 1497632, upload-time = "2026-06-30T20:02:23.517Z" }, + { url = "https://files.pythonhosted.org/packages/42/7e/9daffefcf5b97e6bb4c3e0b3c024c1aee9722f23d3cf7cd2ff80d6fb4a40/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c617417f9cbb0cb144f6283c3cbe0d2e0f01beaf9f608f662b21191058a626ec", size = 1448858, upload-time = "2026-06-30T20:02:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1f/f9baaab81a677ea0af7d2458cac2f94ebcc85958f8a3c15ba9d9e5dab653/ast_serialize-0.6.0-cp314-cp314t-win32.whl", hash = "sha256:5337cb256dcea3df9288205213d1601581536526b8f4da44b6974f1180f3252a", size = 1052600, upload-time = "2026-06-30T20:02:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1f/41b535866519512d8cf6669cb2cff7823b7672bb6279c0333b4ff89d7d9f/ast_serialize-0.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2d947e45cafc4b09bd7528917fa84c517654a43de173c79785574b7b3068ac24", size = 1095570, upload-time = "2026-06-30T20:02:27.639Z" }, + { url = "https://files.pythonhosted.org/packages/50/64/e472fe3e3a2d33d874b987e8518aedf24562919e3b6161a4fa1797e89c0f/ast_serialize-0.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6e15ec740436e1a0d62de848641abe5f3a2f89a7f94907d534795ac91bbacf14", size = 1067267, upload-time = "2026-06-30T20:02:28.949Z" }, + { url = "https://files.pythonhosted.org/packages/52/19/ac8348ae8711c9b5ae834634f635780cab62a0f5e6f988882e048b89c2ae/ast_serialize-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:093cb8bb91b720d8523580498d031791bb1bbaa048599c3d21085d380e11a596", size = 1185367, upload-time = "2026-06-30T20:02:30.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f6/ec7ec652c51db77c2f61d8573338e13e4704303265ccc658cb4031d9f354/ast_serialize-0.6.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:e61580a69faf47e3689795367ed211f2a10fd741478cc0f36a0f128793360aad", size = 1178657, upload-time = "2026-06-30T20:02:31.964Z" }, + { url = "https://files.pythonhosted.org/packages/6f/02/613a7534a41d0122f37d1e0c64aa8ac78bfb831f8c92f6db057a311abb3c/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305802f2ce2a7c4e87835078ea85c58b586ddda8095b92fe2ead9364ae19c80a", size = 1238620, upload-time = "2026-06-30T20:02:33.664Z" }, + { url = "https://files.pythonhosted.org/packages/4d/21/087957bba486242afc52f49b2d9e21c9dad00289356cf9efe67084015a9d/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7b8b8f0c42f752ea00b2b7d7c090b3f80d9c1c5c75cadf16423790a0cc74081", size = 1236075, upload-time = "2026-06-30T20:02:34.936Z" }, + { url = "https://files.pythonhosted.org/packages/82/04/78128bbb170071c2c72a210a181f1c00e11cc1cec60a8beef747b07f9201/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd5b91b9e6f2356ace3a556963b0cd783b395fbbb0bb17b4defc283415466e77", size = 1441348, upload-time = "2026-06-30T20:02:36.245Z" }, + { url = "https://files.pythonhosted.org/packages/64/64/62fb99d6faf199b4c3e5b08a07136e9a0d7664bb249c6de3670e5b63e9b6/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d6ef91590258ada18909b9caea344dac4de2013906b035473cd674a43f4b790", size = 1258580, upload-time = "2026-06-30T20:02:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/ca/87/b4d6c38e0ccd5e85dc54cecdf933a152c60b28fe5d993a6d8a72fa6d5896/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcbed41e9386059fc0261d602445ede0976c2ecec2939688bcbcb9ed0b6f28b7", size = 1261693, upload-time = "2026-06-30T20:02:39.123Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4b/3676ca2191f39bafb75f93f99b2f429ec464586158fece2165f3572805dc/ast_serialize-0.6.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:cdc4e6f930b9090c2f92c9036ad12ffb8e6e44d4a5ba06f1458a05d60f203f7b", size = 1252517, upload-time = "2026-06-30T20:02:40.511Z" }, + { url = "https://files.pythonhosted.org/packages/f3/58/494ef8c4b4acb2f4a265ac934caf45f792a08fe27d6b853de35ad991941a/ast_serialize-0.6.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:897ac47b5637be41c0c07061c8a912fafa967ef1dc73fa115e4bfa70882a093b", size = 1304843, upload-time = "2026-06-30T20:02:41.961Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f2/13736d920ab3d49bbee80ef1a277dd7b7aaf3b3545efd9d2a8114fe05525/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c4af9a1386166e40ed01464991806f89038a2d89782576c7774876fa77034e32", size = 1413698, upload-time = "2026-06-30T20:02:44.179Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5a/e046f3899e2acba4677d7427b76431443a1aa1a0e583dfb05b55b69d55cf/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c901adbd750029b9ac4ad3d6aa56853e0ad4875119fbf52b7b8298afc223828b", size = 1512209, upload-time = "2026-06-30T20:02:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c7/e42aaca7bb2d22a7c06d5a8c7930086c5a334e93d716e6fa5e6647a4515f/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae22a366b752ab4496191525b78b097b5b72d531752e3c1dd7e383a8f2c8a1a", size = 1508464, upload-time = "2026-06-30T20:02:46.942Z" }, + { url = "https://files.pythonhosted.org/packages/95/93/5524a3dc6c3f593de3228ed9cbef73afa047625b7000ec21b7f58e6eb4d4/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4ed29121da8b3fdc291002801a1de0f76248fa07dce89157a5f277842cf6126e", size = 1457164, upload-time = "2026-06-30T20:02:48.294Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c0/36a6ffb4d653cf621427b4c4928671f53ad800c453474de2b82564a44ad9/ast_serialize-0.6.0-cp39-abi3-pyemscripten_2026_0_wasm32.whl", hash = "sha256:b1dac4e09d341c1300ba69cdcbe62867b32a8c75d90db9bf4d083bec3b039f0b", size = 863014, upload-time = "2026-06-30T20:02:49.742Z" }, + { url = "https://files.pythonhosted.org/packages/09/c7/7d5ad8b49e1278e1c2a1e0274bd7850560b3f09313aa00c13bc8d5544792/ast_serialize-0.6.0-cp39-abi3-win32.whl", hash = "sha256:82c312a7844d2fdeb4d5c48bd3d215bf940dafd4704e1a9bcf252a99010a99b1", size = 1063165, upload-time = "2026-06-30T20:02:50.98Z" }, + { url = "https://files.pythonhosted.org/packages/47/ae/6710c14ecb276031cf10249f6adf5a59e2d3fdb3b5183bd59f70524067ee/ast_serialize-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:113b58346f9ceb664352032770caca817d4a3c86f611c6088e6ef65ddaa70f0e", size = 1101444, upload-time = "2026-06-30T20:02:52.554Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/c53deb2cd0c9b0fb636d24d9f40924cf2e65028e6b20b10cd5c1eeb2c730/ast_serialize-0.6.0-cp39-abi3-win_arm64.whl", hash = "sha256:ccd132fe8db56f61fe743b1f644d01b8d65b83248a8da506f3132bda86d6ed5e", size = 1072965, upload-time = "2026-06-30T20:02:54.097Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "floquet-if-manybody" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pyyaml" }, + { name = "scipy" }, +] + +[package.optional-dependencies] +dev = [ + { name = "mypy" }, + { name = "pytest" }, + { name = "ruff" }, +] +nonmarkov = [ + { name = "oqupy" }, +] + +[package.metadata] +requires-dist = [ + { name = "matplotlib", specifier = ">=3.9" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.14" }, + { name = "numpy", specifier = ">=1.26,<2.0" }, + { name = "oqupy", marker = "extra == 'nonmarkov'", specifier = "==0.5.0" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3" }, + { name = "pyyaml", specifier = ">=6.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9" }, + { name = "scipy", specifier = ">=1.14" }, +] +provides-extras = ["dev", "nonmarkov"] + +[[package]] +name = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f", size = 2881793, upload-time = "2026-05-14T12:02:56.645Z" }, + { url = "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9", size = 2428130, upload-time = "2026-05-14T12:02:58.891Z" }, + { url = "https://files.pythonhosted.org/packages/d8/38/6937fbd7f2dc3a6b48725851bc2c15ec949b9af14d9bbcb5fe83cdf9bdf9/fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b", size = 5111952, upload-time = "2026-05-14T12:03:01.263Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18", size = 5082308, upload-time = "2026-05-14T12:03:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/67/00/cdd9d4944ca6ae280d01e69cc37bde3bf663630b837a6fc6d2cd65d80e0e/fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0", size = 5087932, upload-time = "2026-05-14T12:03:05.147Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f1/0aa0dbea778c75adbef223c42019fd47d22262b905974d62d829545d485f/fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007", size = 5213271, upload-time = "2026-05-14T12:03:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/253e4056e1f0e67b9390125a154b73b5eb73ad521bece95c004858fdeec2/fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb", size = 2304473, upload-time = "2026-05-14T12:03:09.271Z" }, + { url = "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c", size = 2356389, upload-time = "2026-05-14T12:03:11.53Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8d/d8fec3dcde2963f8c908fb315e5ff2cd0ac34f82394bbbf73a2aa5145ce3/fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd", size = 2876062, upload-time = "2026-05-14T12:03:32.554Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/d935dc54e4ff121bfdd11e08702db63a7e6f25af21d8a3d7b7212df53641/fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59", size = 2424594, upload-time = "2026-05-14T12:03:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/8e/40/e76320afa1df918e146155ef239b1719ee266092e96f5423bfd075affba1/fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d", size = 5024840, upload-time = "2026-05-14T12:03:36.745Z" }, + { url = "https://files.pythonhosted.org/packages/ce/36/0b805d8c485f872f65a509cbe3b58a5d0d17bee855333b54a150c79d3061/fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68", size = 4975801, upload-time = "2026-05-14T12:03:38.833Z" }, + { url = "https://files.pythonhosted.org/packages/c8/26/2cee03d0aa083ab022da5c07aff9ed3f689da1defb81ad6917c9627896da/fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be", size = 4965009, upload-time = "2026-05-14T12:03:41.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/48/cc4b66d9058c0d0982c833fad10127c4b0e9324606aafa41382295ca4102/fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27", size = 5105892, upload-time = "2026-05-14T12:03:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313, upload-time = "2026-05-14T12:03:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299, upload-time = "2026-05-14T12:03:47.414Z" }, + { url = "https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745", size = 2875338, upload-time = "2026-05-14T12:03:50.052Z" }, + { url = "https://files.pythonhosted.org/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03", size = 2422661, upload-time = "2026-05-14T12:03:52.154Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49", size = 5010526, upload-time = "2026-05-14T12:03:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b", size = 4923946, upload-time = "2026-05-14T12:03:56.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/872e6e233b8c5e8b41413796ff18b7fe479661bd40147e071b450dfad7a1/fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6", size = 4962489, upload-time = "2026-05-14T12:03:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/30/c4/83c24f2ec38b90cfda84bf4b1a1f49df80e84a1db4e7ac6e0d41bf23bc39/fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4", size = 5071870, upload-time = "2026-05-14T12:04:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026, upload-time = "2026-05-14T12:04:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454, upload-time = "2026-05-14T12:04:06.752Z" }, + { url = "https://files.pythonhosted.org/packages/49/4e/652d1580c5f4e39f7d103b0c793e4773129ad633dce4addd0cf4dfebde02/fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001", size = 2958152, upload-time = "2026-05-14T12:04:08.706Z" }, + { url = "https://files.pythonhosted.org/packages/0e/55/ad864c9a9b219f552eb46b32cd7906c466e5a578ba0c3abfcc0fe7413eb6/fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e", size = 2460809, upload-time = "2026-05-14T12:04:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/0aa8db70f18cf52e49b4ed5ecec68547f981160bf5ded3b5aed6faa0a6f9/fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096", size = 5148649, upload-time = "2026-05-14T12:04:12.747Z" }, + { url = "https://files.pythonhosted.org/packages/7f/63/18e4369c25043096f1048e0c9915951adc4f842bd81c6b18155824d6fa99/fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f", size = 4932147, upload-time = "2026-05-14T12:04:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3f/67f3eac2ffd8a98446c5022f8ed3864eac878a5ff7af8df4c8286dba16cc/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40", size = 5027237, upload-time = "2026-05-14T12:04:17.675Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ba/4e6214cb38a7b04779e97bb7636de9a5c7f20af7018d03dee0b64c08510a/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196", size = 5053933, upload-time = "2026-05-14T12:04:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326, upload-time = "2026-05-14T12:04:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829, upload-time = "2026-05-14T12:04:26.829Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + +[[package]] +name = "graphviz" +version = "0.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, +] + +[[package]] +name = "h5py" +version = "3.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/33/acd0ce6863b6c0d7735007df01815403f5589a21ff8c2e1ee2587a38f548/h5py-3.16.0.tar.gz", hash = "sha256:a0dbaad796840ccaa67a4c144a0d0c8080073c34c76d5a6941d6818678ef2738", size = 446526, upload-time = "2026-03-06T13:49:08.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/95/a825894f3e45cbac7554c4e97314ce886b233a20033787eda755ca8fecc7/h5py-3.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:719439d14b83f74eeb080e9650a6c7aa6d0d9ea0ca7f804347b05fac6fbf18af", size = 3721663, upload-time = "2026-03-06T13:47:49.599Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3f0a0e136f2e95dd0b67146abb6668af4f1a69c81ef8651a2d316e8e01de447", size = 3087630, upload-time = "2026-03-06T13:47:51.249Z" }, + { url = "https://files.pythonhosted.org/packages/98/a8/2594cef906aee761601eff842c7dc598bea2b394a3e1c00966832b8eeb7c/h5py-3.16.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a6fbc5367d4046801f9b7db9191b31895f22f1c6df1f9987d667854cac493538", size = 4823472, upload-time = "2026-03-06T13:47:53.085Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fb1720028d99040792bb2fb31facb8da44a6f29df7697e0b84f0d79aff2e9bd3", size = 5027150, upload-time = "2026-03-06T13:47:55.043Z" }, + { url = "https://files.pythonhosted.org/packages/2e/fd/301739083c2fc4fd89950f9bcfce75d6e14b40b0ca3d40e48a8993d1722c/h5py-3.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:314b6054fe0b1051c2b0cb2df5cbdab15622fb05e80f202e3b6a5eee0d6fe365", size = 4814544, upload-time = "2026-03-06T13:47:56.893Z" }, + { url = "https://files.pythonhosted.org/packages/4c/42/2193ed41ccee78baba8fcc0cff2c925b8b9ee3793305b23e1f22c20bf4c7/h5py-3.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ffbab2fedd6581f6aa31cf1639ca2cb86e02779de525667892ebf4cc9fd26434", size = 5034013, upload-time = "2026-03-06T13:47:59.01Z" }, + { url = "https://files.pythonhosted.org/packages/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:17d1f1630f92ad74494a9a7392ab25982ce2b469fc62da6074c0ce48366a2999", size = 3191673, upload-time = "2026-03-06T13:48:00.626Z" }, + { url = "https://files.pythonhosted.org/packages/f2/48/239cbe352ac4f2b8243a8e620fa1a2034635f633731493a7ff1ed71e8658/h5py-3.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:85b9c49dd58dc44cf70af944784e2c2038b6f799665d0dcbbc812a26e0faa859", size = 2673834, upload-time = "2026-03-06T13:48:02.579Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c0/5d4119dba94093bbafede500d3defd2f5eab7897732998c04b54021e530b/h5py-3.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5313566f4643121a78503a473f0fb1e6dcc541d5115c44f05e037609c565c4d", size = 3685604, upload-time = "2026-03-06T13:48:04.198Z" }, + { url = "https://files.pythonhosted.org/packages/b0/42/c84efcc1d4caebafb1ecd8be4643f39c85c47a80fe254d92b8b43b1eadaf/h5py-3.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:42b012933a83e1a558c673176676a10ce2fd3759976a0fedee1e672d1e04fc9d", size = 3061940, upload-time = "2026-03-06T13:48:05.783Z" }, + { url = "https://files.pythonhosted.org/packages/89/84/06281c82d4d1686fde1ac6b0f307c50918f1c0151062445ab3b6fa5a921d/h5py-3.16.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ff24039e2573297787c3063df64b60aab0591980ac898329a08b0320e0cf2527", size = 5198852, upload-time = "2026-03-06T13:48:07.482Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/1a19e42cd43cc1365e127db6aae85e1c671da1d9a5d746f4d34a50edb577/h5py-3.16.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:dfc21898ff025f1e8e67e194965a95a8d4754f452f83454538f98f8a3fcb207e", size = 5405250, upload-time = "2026-03-06T13:48:09.628Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/9790c1655eabeb85b92b1ecab7d7e62a2069e53baefd58c98f0909c7a948/h5py-3.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:698dd69291272642ffda44a0ecd6cd3bda5faf9621452d255f57ce91487b9794", size = 5190108, upload-time = "2026-03-06T13:48:11.26Z" }, + { url = "https://files.pythonhosted.org/packages/51/d7/ab693274f1bd7e8c5f9fdd6c7003a88d59bedeaf8752716a55f532924fbb/h5py-3.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b2c02b0a160faed5fb33f1ba8a264a37ee240b22e049ecc827345d0d9043074", size = 5419216, upload-time = "2026-03-06T13:48:13.322Z" }, + { url = "https://files.pythonhosted.org/packages/03/c1/0976b235cf29ead553e22f2fb6385a8252b533715e00d0ae52ed7b900582/h5py-3.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:96b422019a1c8975c2d5dadcf61d4ba6f01c31f92bbde6e4649607885fe502d6", size = 3182868, upload-time = "2026-03-06T13:48:15.759Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/866b7e570b39070f92d47b0ff1800f0f8239b6f9e45f02363d7112336c1f/h5py-3.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:39c2838fb1e8d97bcf1755e60ad1f3dd76a7b2a475928dc321672752678b96db", size = 2653286, upload-time = "2026-03-06T13:48:17.279Z" }, + { url = "https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:370a845f432c2c9619db8eed334d1e610c6015796122b0e57aa46312c22617d9", size = 3671808, upload-time = "2026-03-06T13:48:19.737Z" }, + { url = "https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb", size = 3045837, upload-time = "2026-03-06T13:48:21.854Z" }, + { url = "https://files.pythonhosted.org/packages/da/1e/6172269e18cc5a484e2913ced33339aad588e02ba407fafd00d369e22ef3/h5py-3.16.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:099f2525c9dcf28de366970a5fb34879aab20491589fa89ce2863a84218bb524", size = 5193860, upload-time = "2026-03-06T13:48:24.071Z" }, + { url = "https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402", size = 5400417, upload-time = "2026-03-06T13:48:25.728Z" }, + { url = "https://files.pythonhosted.org/packages/bc/81/5b62d760039eed64348c98129d17061fdfc7839fc9c04eaaad6dee1004e4/h5py-3.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:171038f23bccddfc23f344cadabdfc9917ff554db6a0d417180d2747fe4c75a7", size = 5185214, upload-time = "2026-03-06T13:48:27.436Z" }, + { url = "https://files.pythonhosted.org/packages/28/c4/532123bcd9080e250696779c927f2cb906c8bf3447df98f5ceb8dcded539/h5py-3.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7e420b539fb6023a259a1b14d4c9f6df8cf50d7268f48e161169987a57b737ff", size = 5414598, upload-time = "2026-03-06T13:48:29.49Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:18f2bbcd545e6991412253b98727374c356d67caa920e68dc79eab36bf5fedad", size = 3175509, upload-time = "2026-03-06T13:48:31.131Z" }, + { url = "https://files.pythonhosted.org/packages/a5/23/bb8647521d4fd770c30a76cfc6cb6a2f5495868904054e92f2394c5a78ff/h5py-3.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:656f00e4d903199a1d58df06b711cf3ca632b874b4207b7dbec86185b5c8c7d4", size = 2647362, upload-time = "2026-03-06T13:48:33.411Z" }, + { url = "https://files.pythonhosted.org/packages/48/3c/7fcd9b4c9eed82e91fb15568992561019ae7a829d1f696b2c844355d95dd/h5py-3.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9c9d307c0ef862d1cd5714f72ecfafe0a5d7529c44845afa8de9f46e5ba8bd65", size = 3678608, upload-time = "2026-03-06T13:48:35.183Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8c1eff849cdd53cbc73c214c30ebdb6f1bb8b64790b4b4fc36acdb5e43570210", size = 3054773, upload-time = "2026-03-06T13:48:37.139Z" }, + { url = "https://files.pythonhosted.org/packages/58/a5/4964bc0e91e86340c2bbda83420225b2f770dcf1eb8a39464871ad769436/h5py-3.16.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e2c04d129f180019e216ee5f9c40b78a418634091c8782e1f723a6ca3658b965", size = 5198886, upload-time = "2026-03-06T13:48:38.879Z" }, + { url = "https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:e4360f15875a532bc7b98196c7592ed4fc92672a57c0a621355961cafb17a6dd", size = 5404883, upload-time = "2026-03-06T13:48:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f2/58f34cb74af46d39f4cd18ea20909a8514960c5a3e5b92fd06a28161e0a8/h5py-3.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3fae9197390c325e62e0a1aa977f2f62d994aa87aab182abbea85479b791197c", size = 5192039, upload-time = "2026-03-06T13:48:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ca/934a39c24ce2e2db017268c08da0537c20fa0be7e1549be3e977313fc8f5/h5py-3.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:43259303989ac8adacc9986695b31e35dba6fd1e297ff9c6a04b7da5542139cc", size = 5421526, upload-time = "2026-03-06T13:48:44.838Z" }, + { url = "https://files.pythonhosted.org/packages/3e/14/615a450205e1b56d16c6783f5ccd116cde05550faad70ae077c955654a75/h5py-3.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:fa48993a0b799737ba7fd21e2350fa0a60701e58180fae9f2de834bc39a147ab", size = 3183263, upload-time = "2026-03-06T13:48:47.117Z" }, + { url = "https://files.pythonhosted.org/packages/7b/48/a6faef5ed632cae0c65ac6b214a6614a0b510c3183532c521bdb0055e117/h5py-3.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:1897a771a7f40d05c262fc8f37376ec37873218544b70216872876c627640f63", size = 2663450, upload-time = "2026-03-06T13:48:48.707Z" }, + { url = "https://files.pythonhosted.org/packages/5d/32/0c8bb8aedb62c772cf7c1d427c7d1951477e8c2835f872bc0a13d1f85f86/h5py-3.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15922e485844f77c0b9d275396d435db3baa58292a9c2176a386e072e0cf2491", size = 3760693, upload-time = "2026-03-06T13:48:50.453Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1f/fcc5977d32d6387c5c9a694afee716a5e20658ac08b3ff24fdec79fb05f2/h5py-3.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:df02dd29bd247f98674634dfe41f89fd7c16ba3d7de8695ec958f58404a4e618", size = 3181305, upload-time = "2026-03-06T13:48:52.221Z" }, + { url = "https://files.pythonhosted.org/packages/f5/a1/af87f64b9f986889884243643621ebbd4ac72472ba8ec8cec891ac8e2ca1/h5py-3.16.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:0f456f556e4e2cebeebd9d66adf8dc321770a42593494a0b6f0af54a7567b242", size = 5074061, upload-time = "2026-03-06T13:48:54.089Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d0/146f5eaff3dc246a9c7f6e5e4f42bd45cc613bce16693bcd4d1f7c958bf5/h5py-3.16.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:3e6cb3387c756de6a9492d601553dffea3fe11b5f22b443aac708c69f3f55e16", size = 5279216, upload-time = "2026-03-06T13:48:56.75Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9d/12a13424f1e604fc7df9497b73c0356fb78c2fb206abd7465ce47226e8fd/h5py-3.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8389e13a1fd745ad2856873e8187fd10268b2d9677877bb667b41aebd771d8b7", size = 5070068, upload-time = "2026-03-06T13:48:59.169Z" }, + { url = "https://files.pythonhosted.org/packages/41/8c/bbe98f813722b4873818a8db3e15aa3e625b59278566905ac439725e8070/h5py-3.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:346df559a0f7dcb31cf8e44805319e2ab24b8957c45e7708ce503b2ec79ba725", size = 5300253, upload-time = "2026-03-06T13:49:02.033Z" }, + { url = "https://files.pythonhosted.org/packages/32/9e/87e6705b4d6890e7cecdf876e2a7d3e40654a2ae37482d79a6f1b87f7b92/h5py-3.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4c6ab014ab704b4feaa719ae783b86522ed0bf1f82184704ed3c9e4e3228796e", size = 3381671, upload-time = "2026-03-06T13:49:04.351Z" }, + { url = "https://files.pythonhosted.org/packages/96/91/9fad90cfc5f9b2489c7c26ad897157bce82f0e9534a986a221b99760b23b/h5py-3.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:faca8fb4e4319c09d83337adc80b2ca7d5c5a343c2d6f1b6388f32cfecca13c1", size = 2740706, upload-time = "2026-03-06T13:49:06.347Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, +] + +[[package]] +name = "librt" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/2f/3908645ddddab7120b46295e541ead308109fa48dbec7d67d7a778870d60/librt-0.13.0.tar.gz", hash = "sha256:1d2a610c14ac0d0750ee0a3ab8548e83155258387891caaca04def4bf7289781", size = 211402, upload-time = "2026-07-08T12:26:29.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/25/a6498964cfeec270c468cffdc118f69c29b412593610d55fa1327ca51ff4/librt-0.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1b5a7bbff495baedbd9b916c367d66854008f8f3b575908ded477c499dc60082", size = 148029, upload-time = "2026-07-08T12:24:45.961Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/dc86d1bffd8e0c2818bace29d9f7783cfbb8e0673bf3673b5bbd5bbe0420/librt-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34bc7938b9fdf14fe32a406c19c71faf894c5cee7e7474bd0be2f17200b82d14", size = 153036, upload-time = "2026-07-08T12:24:47.257Z" }, + { url = "https://files.pythonhosted.org/packages/29/3f/b923826660f02f286186cd9303d52bb05ced0a13708edc104dc8480920e3/librt-0.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f40e56b61b41be5f7dec938cfeffd660668cf4b5e72c78e7bd671d66b7bc2c79", size = 493062, upload-time = "2026-07-08T12:24:48.483Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/6c0980a9c9b1302cb68d108906697b89eceb55889bb1dcf77c109aa56ca5/librt-0.13.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:9c5d02b89de5acd0379a51ec44a89476fb03df6145442e1c8ecd6bee2f91b176", size = 485510, upload-time = "2026-07-08T12:24:49.727Z" }, + { url = "https://files.pythonhosted.org/packages/32/81/795ae3b9df5dd94079fb807e38191855e023e8c6249014ae6bc3f0d9a490/librt-0.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7db9a3ff32ef5f7d1703d93831a3316cdf0b537de6a1cc03cc8fdd09b9194e89", size = 515909, upload-time = "2026-07-08T12:24:51.135Z" }, + { url = "https://files.pythonhosted.org/packages/20/e5/182de15abce8907108a6fdb41487de65beb5099b74dc5841b19b099168db/librt-0.13.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3dbb2a31882456cadc7053378e81ad7ed7693db4ac9f98ab5f81ef034aa8ec9f", size = 508620, upload-time = "2026-07-08T12:24:52.358Z" }, + { url = "https://files.pythonhosted.org/packages/32/03/33978d32db76e1f66377e8f78e42a2ca3c162143331677d1f50bbad36cfb/librt-0.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c6014e3c80f9c1fe268ef8b0e0ef113bac672cc032f2f93866e7ddad4f3e663d", size = 530363, upload-time = "2026-07-08T12:24:53.503Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f5/b291fbd2d00f7d8287bcbf67b5aa0c6afed4bc26cef23e079629c47a2c04/librt-0.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:091b60a4d2174fc1ec5c34cdc0b72efb6224753d76b7da61ebeab7a191aec8bd", size = 534209, upload-time = "2026-07-08T12:24:55.138Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/6f41f17939d191bc21609f220da8509316bc62797f078545fe83be522e78/librt-0.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:66cb1138f384a191a6d75f986064841fcfdc0cea98f7bd9c9ab9b38049917588", size = 514254, upload-time = "2026-07-08T12:24:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/af/c2/2e4befa5410a7443019c14abccc94ff619797171f6b72013635fb87f31d7/librt-0.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:17221a7569f8f292aa0014226e48aa25b8c2b08da18088cd230953d0ea0f9cd1", size = 557611, upload-time = "2026-07-08T12:24:57.561Z" }, + { url = "https://files.pythonhosted.org/packages/ab/54/8b69f81448417adbc040a2185f4e2eece1e1994b7dcfaeed4662b30f98a5/librt-0.13.0-cp311-cp311-win32.whl", hash = "sha256:fc67741da44c6eaa90e01eafb586bbba9b51eb5b6ed381ee6f5ae72eb3316d21", size = 104906, upload-time = "2026-07-08T12:24:58.806Z" }, + { url = "https://files.pythonhosted.org/packages/76/5a/f4aaf37b50f2fde12c8c663b83fdd499cdc24f957f19543d7414bfcc9e25/librt-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc99dfb62b23c9207c33d0be8a2e2af7a42e21e6ea388b380a0c948c7b88953b", size = 125852, upload-time = "2026-07-08T12:25:00.065Z" }, + { url = "https://files.pythonhosted.org/packages/f2/99/bf1820e6feeabc2f218c24450ec0c995d6a91e8ba0fd3caf042c9e8adb2a/librt-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:40ccd13c252d3fe473ffc8a57be7565abc8b64cf1b108344c859d5164f7f3e0c", size = 111832, upload-time = "2026-07-08T12:25:01.148Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/b2933ddae222dac338476abb872641169a5cfed2c2bb5444a5b07b32b0c3/librt-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30536798f4504c0fad0885b1d371b0539abb081e4570c9d7c641cb51141b49f0", size = 150990, upload-time = "2026-07-08T12:25:02.42Z" }, + { url = "https://files.pythonhosted.org/packages/90/ef/db98f744ca50e6efc9c95c70ee49b77aefac31f6a3fc7c83754a42d6a74f/librt-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93d24ebb82aa4420b1409c389e7857bc35bd0b668007ac8172427d5c73cc8cc5", size = 155238, upload-time = "2026-07-08T12:25:03.681Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/a197e7bc72baf2c61ce7fdc6906a5054dc05bd8da0819aa894e4857bf87e/librt-0.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb8a1adce42d8b75485a5d56a9623a50bcab995b6079f1dac59fc44034dd93d9", size = 503073, upload-time = "2026-07-08T12:25:05.049Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e7/7887712e27da7c1ab80fcabb1de6eb24243964f6557cae530d4b70706dbd/librt-0.13.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0763ca2ab66058174f9dee426dc64f5e0a89c24a7df8d3fe3f1836c04e25de4b", size = 496528, upload-time = "2026-07-08T12:25:06.26Z" }, + { url = "https://files.pythonhosted.org/packages/94/f0/f2283385bb6b950b26a1410f4ce51ec27231e0b3a4b925c46366d218b198/librt-0.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b222493da6e7b6199db9bd79502436cf5a27da3c1f7fa83c7e285444fc93fd03", size = 531786, upload-time = "2026-07-08T12:25:07.658Z" }, + { url = "https://files.pythonhosted.org/packages/36/11/69ac3b54766ffba5fd7e5acebfb048d66dbe1f9f2d14516c2b3edc59cf87/librt-0.13.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fadc63331f4388c3dc90090448f682a7e9feafc11481391c1e94f2f907a3976e", size = 524393, upload-time = "2026-07-08T12:25:09.121Z" }, + { url = "https://files.pythonhosted.org/packages/61/5f/d72f95fd444a926a3c14b4e24979474116988dd57a45be242077c45d3c22/librt-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:70d9c62a4cffd9f23396cd5ef93fc5d11b31596b9b7d6306074abe3d5fcf09bd", size = 543026, upload-time = "2026-07-08T12:25:10.459Z" }, + { url = "https://files.pythonhosted.org/packages/c4/08/dcd9993ad192737a004ba263d549f8ea605b326b952e7d6205c7d4170b76/librt-0.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66c0e7e6b02a155576df2c77ec933a70b72da726e248c494abf690923e624348", size = 546829, upload-time = "2026-07-08T12:25:11.716Z" }, + { url = "https://files.pythonhosted.org/packages/96/d5/6d9bb2f54e4109a956b7128836529653eb9d740f784bc47ed10a02c1000e/librt-0.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ac04bcd3328eb91d99dfedf6a60d9c1f15d3434e6f6daf922f0420f7d90b85c7", size = 535700, upload-time = "2026-07-08T12:25:13.144Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f2/10946922503858a359492fa27f13e86228bde702116a740ac7b3cd185f24/librt-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db327e7271e653c32040b85ae6188059c924b57d7e1e29f935523fa017cd4e82", size = 573566, upload-time = "2026-07-08T12:25:14.336Z" }, + { url = "https://files.pythonhosted.org/packages/48/a8/94f00e3c99479a18088af3685ea016c42f3c7d5d1964d8dbb40c08d7f1aa/librt-0.13.0-cp312-cp312-win32.whl", hash = "sha256:860bd1d8ba48456ce08feaf8d343a8aaeb2fa086f2bcaa2a923fa3f7a3ff9aa3", size = 106099, upload-time = "2026-07-08T12:25:16.159Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7b/2da9c74c1ed25a89cc4e1c8e007ea2eb4a0f1fafa3e70d757fe3242c5c5c/librt-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:e54a315caf843c8d77e388cadc56ea9ded569935ee2d2347d7ea94992e5aa6fa", size = 126934, upload-time = "2026-07-08T12:25:17.275Z" }, + { url = "https://files.pythonhosted.org/packages/d0/65/aead61bbf3b5358593f9d4779d2a0e88eaf6ec191a6342dde36dd1df6371/librt-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:c718e99a0992127af84385378460db624103b559ab260435abcfe77a4e4ed1c1", size = 112236, upload-time = "2026-07-08T12:25:18.425Z" }, + { url = "https://files.pythonhosted.org/packages/67/3b/18e7b63255297a2bdc9c25c8d6d4ca8eca9f63aceb1252c0f7427ac7099e/librt-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a468951af16155824e88bdd8326ebe5bdb371f3ec0ac04642994b98201d914f3", size = 151027, upload-time = "2026-07-08T12:25:19.638Z" }, + { url = "https://files.pythonhosted.org/packages/4d/68/e2248452c00d1a03b45fee1752cdc8f790a476efd2402b75181da88a9e61/librt-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ae01d8512cc17079e53425635327dbf3f7ff57a42c00dec348bf79791c56444c", size = 155152, upload-time = "2026-07-08T12:25:20.851Z" }, + { url = "https://files.pythonhosted.org/packages/0e/16/52b1c99bf19057a062aac39c900cbb81499f6f75d6c537c14463d247ba78/librt-0.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32c26893cd085c1efe83219e78d866da23fb20a066101b8f68210004361d224c", size = 502499, upload-time = "2026-07-08T12:25:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/9f/54/b811151805c795f55e0dedee6ec687b75f9982a8105d240ea3910737a77b/librt-0.13.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5929da1981a46bcf4b28b1b9499905f0ff58e2419da402a048234e9783acbc4b", size = 496108, upload-time = "2026-07-08T12:25:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f8/094d6b2bd93f3fdaa54db54cc788c4a365333bddad65ab02e04da0b1d004/librt-0.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b85d664d777bab6c0d709416cb42938251fda9e221b79e3a2215d85df5f4f9", size = 531576, upload-time = "2026-07-08T12:25:24.648Z" }, + { url = "https://files.pythonhosted.org/packages/2e/40/541733d5755824f968f7ec39d78ffbd75d145964157ae5e69a09ec6d7326/librt-0.13.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:531b2df3e9fe96b1fcf73a6d165921e4656be5f58d631d384ebce344298368db", size = 524390, upload-time = "2026-07-08T12:25:25.898Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b5/255673cfdbf5ba663339d36cd863c897289ab4337577e19f9405ce059f36/librt-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:109b84a9edf69ad89dc1f66358659e14a031baca95e3e5b0060bd903ede8efd6", size = 543053, upload-time = "2026-07-08T12:25:27.436Z" }, + { url = "https://files.pythonhosted.org/packages/9e/11/ab5005e9c9850710f21e354201bf090646349d3fabf5f951eaf70235729e/librt-0.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1304368a3e7ffc3e9db986796cc5326fdb5943a3567ecc137cff318e4240c0e7", size = 546387, upload-time = "2026-07-08T12:25:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/a2/04/a5d7ce1d1df1afd15ca283dcdf7530ac073e12d69ae8c40879dda96f7868/librt-0.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e4f9b472e7d308d94b62c801982065661158c6ed02790d6c7ddb4337cea0f9c1", size = 535970, upload-time = "2026-07-08T12:25:30.171Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/927e267a6daa290174ac281b23c9804c8829b042ade9c6f24a065f540958/librt-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f836c37478f167a81200d8c8b2c920a22224564bed2c23d7aeec760965c367a", size = 573582, upload-time = "2026-07-08T12:25:31.507Z" }, + { url = "https://files.pythonhosted.org/packages/10/24/b6c5213efe39c19f9e13605644d0cf063b4ddaa33ac2e45b088e23a70e2e/librt-0.13.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:4000d961ff9598ac6ea603c6c836a5ed49bc205ade5fc378b998dfe1e2c36628", size = 82189, upload-time = "2026-07-08T12:25:32.675Z" }, + { url = "https://files.pythonhosted.org/packages/4c/00/d29736be177a906ac0b84a5b04b4fbfa22c776dc2f366de4172b0f968c08/librt-0.13.0-cp313-cp313-win32.whl", hash = "sha256:79e44cff71750d299d61a678e49995b0d5935a9cda238c2574daeca3ba536927", size = 106193, upload-time = "2026-07-08T12:25:33.692Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ac/aff6fb45393cb8912f39dfb156ef6b2d1cadb207ff465fc8f66141054be8/librt-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:54dab44a847d5ad1acd05c8a83fe518ae685516ecf4d3f7cc6e3df2a66767650", size = 126962, upload-time = "2026-07-08T12:25:34.769Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3a/d68cb2b334d53fd30fac81d3a489ce4ba0d9506f4df43fcf676b68352b19/librt-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:d4cb6fbfdf874340ab5e51450753c0f817b6958a3621125ee695bbc3de866566", size = 112127, upload-time = "2026-07-08T12:25:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/7b/66/f49ae0d592bd45b6941e9a8bafcb6a87cddcd501ee7874707e767f01b585/librt-0.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:25218d94b1d2cbc0ba1d8a3f9dc9af578d9646e5ed16443a70cde1dfdcce6d71", size = 149818, upload-time = "2026-07-08T12:25:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/3d/50/51c76d74014d04fb95b6506d286808984b78a2f7a41039094e6b2194ac48/librt-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f26629539d4893c2957a16c41bb058e1e135c1f150f6a2e25ed047f64cf3f5c6", size = 154071, upload-time = "2026-07-08T12:25:39.399Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fe/f19b0f5f82d5a1f2da736586bc840abd00ce07d6388136ae80b7333883fc/librt-0.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4517d47b2b8af26975a406fba7d314de9696d864252e0257c6ea90238cfe27f", size = 494168, upload-time = "2026-07-08T12:25:40.641Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/b8550c75775127fd31a5f20e8775997f7b527ad661fc8ddccd7497c064f7/librt-0.13.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f19e181de5b3a1148bb3420b8c4b0b0ea0fce6950099724ad151d6cea5acc180", size = 491054, upload-time = "2026-07-08T12:25:41.905Z" }, + { url = "https://files.pythonhosted.org/packages/30/14/4d0204867623df3f33f86efd3d3692ba5e01321443f4d6eab35a22697618/librt-0.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22034924f5b42d5a56371cf271771bfeaabf235a7a8b6264bef2d20013f786c6", size = 523006, upload-time = "2026-07-08T12:25:43.327Z" }, + { url = "https://files.pythonhosted.org/packages/19/0a/c45fc9a260934696bace1ac5df1e148ac92bd71767aee3bf7cd7a4534f4c/librt-0.13.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7897db4e95e22468bdda33d8e012ceacd0182abf001e6389d763f0def6286b9", size = 515058, upload-time = "2026-07-08T12:25:44.541Z" }, + { url = "https://files.pythonhosted.org/packages/13/0a/50c5ce45b326854ef8fa6ae4c36cf5142e5c55315eaf9e51d0ae73ac4da3/librt-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ce61b3746545029d4f5c17d6bd74b676254ad98433086c846ffb5e8fa73f007", size = 534025, upload-time = "2026-07-08T12:25:45.825Z" }, + { url = "https://files.pythonhosted.org/packages/89/2d/08c413c8f93fc13b8103624fce38e5caa86cd08cbbc8465870ab287af54b/librt-0.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:46c330e82565962c761dbce7941be2cff7db674ee807455a8d0cadc5f9b759b0", size = 540557, upload-time = "2026-07-08T12:25:47.059Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/93af71fb4a364952210051811dd4e40174e79656b050c89cacac18af3330/librt-0.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:375f5af8f99cbaa99dd293af986e3d57caabc9ba81a5d3f021603764854197a1", size = 523201, upload-time = "2026-07-08T12:25:48.392Z" }, + { url = "https://files.pythonhosted.org/packages/c1/6e/9766f07b676a4889d9f8bc2864e9ba5fff165653143ef4dda7df6aa34d16/librt-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9320d34c3376ae204b2cd176e8d4883a013934e0aef822f1aed9c536490c275d", size = 565740, upload-time = "2026-07-08T12:25:49.678Z" }, + { url = "https://files.pythonhosted.org/packages/a2/1e/664e3472ce2b6e10e9b83f29d4a36eb982ff6b5a169ae7567bba3a4c4ff5/librt-0.13.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:9af313c66157a69dc69ea0059a66961692250e0dc95af9c385a48ffb770a0d16", size = 81611, upload-time = "2026-07-08T12:25:50.857Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/8582a4d65e2234673685e07309d02c230b28a85724eb0acbf13f019b7f6e/librt-0.13.0-cp314-cp314-win32.whl", hash = "sha256:f2a7253458e34f33543551394ae4fe104b497ec2a65ac266074de64c1df82e37", size = 100106, upload-time = "2026-07-08T12:25:52.03Z" }, + { url = "https://files.pythonhosted.org/packages/63/ce/0cb99efe6086b46cd985dc26672166fae312a239690e75871f7fafbd3fc5/librt-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:a3dfe4edf10e8ed7e55b026a8bfc2c2a8704218b659cd4bffdf604fab966dc39", size = 121209, upload-time = "2026-07-08T12:25:53.166Z" }, + { url = "https://files.pythonhosted.org/packages/26/85/4f3ccb083a3c9b0d42e223acdb3c3f507953324a59cdcab4826e8e2e3b89/librt-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:68a5faee4bba381cb93b5961f684a514cf0053cb92308ff9c792c2fea0b174c6", size = 106404, upload-time = "2026-07-08T12:25:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/b2/77/333191499538c8e8189de7a4cba8e6f49ee949fd6d6e6324b21fd1522466/librt-0.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a38fb81d8376dfa2f8963b265fec07637802b0d01e2a127c19c66cb070fb24f5", size = 159231, upload-time = "2026-07-08T12:25:55.432Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9e/2aa83758f22c278b837a1d8025898434ce2b8bff36678d5330ecaef56dff/librt-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d4c8d9bd5abce34b2e75edb3bf37ab0f34e49b1f915a40ae8468eb7c85bc5b46", size = 161300, upload-time = "2026-07-08T12:25:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c0/86791e936553ca763d6b3c2fb4d31d596cd00e14fa631c283a40ba01559a/librt-0.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:387e2f1d27e89bffe0d3f520f0da0662c973fd607ca16c1808f8a5085419485e", size = 582056, upload-time = "2026-07-08T12:25:58.144Z" }, + { url = "https://files.pythonhosted.org/packages/a8/d3/a9ec15984a185e000c4d2a16ba28bd623124ad4c38a10974c7ff78e3a893/librt-0.13.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:4f6db193d2e5e0ed60359b9a5a682cd67205d0d3b1e459a867dd4b5c4e7eaa7a", size = 562758, upload-time = "2026-07-08T12:25:59.544Z" }, + { url = "https://files.pythonhosted.org/packages/3c/af/dbe36b78b19c06a55097f99305e4ea9458e2273e6ae16a3cbecaad7ee978/librt-0.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d38604854e8d22faadf683ec6c02bb0f886e2ba56ef981a1c36ee275f21ea22", size = 602095, upload-time = "2026-07-08T12:26:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a8/2966891b4dd2830f5203fbee92ac2c4947653a2390ba73dfa44244fad025/librt-0.13.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:371f7ce73026815dafd51c50ce38416e91428b28c4b2ec97cd39271164b0045c", size = 593452, upload-time = "2026-07-08T12:26:02.352Z" }, + { url = "https://files.pythonhosted.org/packages/61/f5/4df8bfc8405ecf8c0d525b4d69636f694bdd8620b313ec8b76e54a5926cc/librt-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3aaedf52171bee90860704c560bc798fe83b76247df47568e0197e9b13c735a0", size = 623729, upload-time = "2026-07-08T12:26:04.294Z" }, + { url = "https://files.pythonhosted.org/packages/d6/13/9ac202dffc8db06f75d06c08c2f9f6ff054be67d21272dcc078fa1cc0c57/librt-0.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:96bad8725a4f196a798366c25ce075d1f7543a4ec045ffc13e6a7ec095cdab04", size = 617077, upload-time = "2026-07-08T12:26:05.845Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f0/ebe38610716aee5cb28efd95089bb90192096179802779381e1c5dcf239c/librt-0.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6bf6a559ffe4a93bbea6cf31ddf01a7fd9ba342ef51f27beb178e318b74acd61", size = 599561, upload-time = "2026-07-08T12:26:07.21Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5c/c2e72e236fff7abc716d5b1753b8b8cd3ea85ac46fe17d2e7c51d4e1c723/librt-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:301067672387902c55f94b51d5022304b36c966ea9fe1f21caab99a9bef487c9", size = 645511, upload-time = "2026-07-08T12:26:08.562Z" }, + { url = "https://files.pythonhosted.org/packages/0c/99/6203ce619dee940d6bfbe099ec3fe4be00a68e9d60f70abf906cf124fe66/librt-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:5fdcf34f86de8fb66d7dc7589f96ba91c4aa46671200d400e6fd6f109a483f18", size = 104357, upload-time = "2026-07-08T12:26:09.828Z" }, + { url = "https://files.pythonhosted.org/packages/52/dd/843b6314087c41657c7036d7914d8f294bdf9b580aa8513ea0588c8e9a3d/librt-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:260c33e92263fa629b4f6d3c51967a1c2158fe6c33237aaa3ebeac586b085259", size = 126998, upload-time = "2026-07-08T12:26:10.975Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/3dcec2884ba1b0806d1408612555c38dd5d68e90156b59f75f6e36435c3a/librt-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2f281549a4c52ac7bb97997f14353f8bd0e53a34ca0dad1c905cfd0b4a58ae99", size = 110771, upload-time = "2026-07-08T12:26:12.303Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/64/f9a391af28f518b11ad45a8a712353c94a0aefce09d3703200e5c54b610a/matplotlib-3.11.1.tar.gz", hash = "sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30", size = 32612045, upload-time = "2026-07-18T03:39:46.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/d0/791aa183dd88491555cf7d4be0b52b0bcf6c3c2a2c22c815a2e819bf53e2/matplotlib-3.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b7cf158e7add54a8d51ac9b5a84abd6d4e13ed4951b4f25f1c5139f41c2addb2", size = 9440302, upload-time = "2026-07-18T03:38:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/35/74/82bbdf683a301f4478384c8aaba6903631a2ca18294b2d7655c9a542bffb/matplotlib-3.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2ace7273b9a5061a3b420918a16fae1f2dc5dfee1abcc13aba71b5d94b1820c", size = 9268549, upload-time = "2026-07-18T03:38:06.144Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/9b4298911303f74e6d83e64a81d996c0616405ec95046fac7f17e4258b9e/matplotlib-3.11.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee55e9041211bf84302ab55ec3965df18dd90ae19f8b58332a7feaf208bfe83", size = 10024922, upload-time = "2026-07-18T03:38:08.236Z" }, + { url = "https://files.pythonhosted.org/packages/84/6f/0bc3c3d05b021db44c14bc379a7c0df7d57302aa15380c16fd4e63fd6a9b/matplotlib-3.11.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f4bdeea33a8d15a071dbfe6d119451b1d719c733ac666d65357082901a9099", size = 10832170, upload-time = "2026-07-18T03:38:10.276Z" }, + { url = "https://files.pythonhosted.org/packages/db/4d/e375f39acdb2af5a9342730618608e39790ec842e6f1b392863028781459/matplotlib-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b4c78ceb2f11bcac7389d305cda17aeb1f4586a857854ab5780bd3dd8dbfc407", size = 10916701, upload-time = "2026-07-18T03:38:12.512Z" }, + { url = "https://files.pythonhosted.org/packages/bc/be/fa26ed085b41298f64a8f9b7592c671bbf1acc8b0df124c1c5de96b859f8/matplotlib-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f33a781e12b1e53b278deb2f5373c2e55ec4f10727be3440c0cfb5cda9f944f", size = 9315331, upload-time = "2026-07-18T03:38:14.949Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/eb5bdf3b6e191b200db298b08bbc1638b7f3c82cdc8680f9d88bf72559ae/matplotlib-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:67e4c3cd578c65ebd81bdc09a1b6592ceafee6dfafe116dc85dfcb647b5bbb18", size = 9003475, upload-time = "2026-07-18T03:38:17.205Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6c/7ef7ebcb2bd9739b2b66b18b076e077f44bb46fdbe28ca0506edb3c62c79/matplotlib-3.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74", size = 9453849, upload-time = "2026-07-18T03:38:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/6d0c312c8d9738e7d9677f09fe5c986b3239e651a7b73a2deb38b65e4a71/matplotlib-3.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b", size = 9283113, upload-time = "2026-07-18T03:38:21.95Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cf/b4ad2cc81b6672ea29ea04e64e350a9f9b493b0908ccd884c67eeff8f7b2/matplotlib-3.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea", size = 10035615, upload-time = "2026-07-18T03:38:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/4e10e033d9b66589d8ed98b84c95cdbb57033d57c1f41339d7393dbd2f2e/matplotlib-3.11.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472", size = 10842559, upload-time = "2026-07-18T03:38:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/88/eb/799612d0f8cd3e816a10fec59329fca52cd2353264df80378dfc541ae855/matplotlib-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481", size = 10927532, upload-time = "2026-07-18T03:38:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/88/89/56649bbaa2fd12e20f3be03dbcc135b0c8676d88bac17977599e3eb442a0/matplotlib-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88a2a27dd9691ae448dfae4b26f59036be90c3c28757edd3553a29559d00859f", size = 9333886, upload-time = "2026-07-18T03:38:30.477Z" }, + { url = "https://files.pythonhosted.org/packages/c1/11/4d124efbbad677b7b7552f6f85a3bd432d4232f95400cea98fcd2ae36ef3/matplotlib-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:480194afceca4df2f137c2721227d3cba67121fbf4397b69cee7f83714b0a58a", size = 9007545, upload-time = "2026-07-18T03:38:32.833Z" }, + { url = "https://files.pythonhosted.org/packages/04/6c/4798363b7fb5644e309fe1fac30216e9146c9f70859d80d588c18caf5317/matplotlib-3.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6771b0cd7838c6a857a7209814158c0ad09bfef878db3033dd82d70ad101f191", size = 9454341, upload-time = "2026-07-18T03:38:35.001Z" }, + { url = "https://files.pythonhosted.org/packages/59/98/6acadbe7f98df19d274bc107ac58bb439fa75df82c33dc110d71a4a8501f/matplotlib-3.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2abdee5ffa2fe11b2d19f7a5c63b785fb7c28cc46c7bc1814156341d9d1a33e1", size = 9283627, upload-time = "2026-07-18T03:38:37.061Z" }, + { url = "https://files.pythonhosted.org/packages/24/ea/65cec46fe241390ccea1b1754207ee28eb71c5ab866bd5f22fe47e538fa4/matplotlib-3.11.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0a19dcf73406d3746d25a5ed42d713604c9a3e024d129b102852b0d941cb9f3", size = 10035860, upload-time = "2026-07-18T03:38:39.663Z" }, + { url = "https://files.pythonhosted.org/packages/c7/10/63fdccccbabe002fb0960876baabc5e3f24d9c1bb4cfb25651457f74b3a0/matplotlib-3.11.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7389b77ed2ab0552f46d9a90b81b7b8e6dfcdc42adc36c37a0865799843e0e3e", size = 10843594, upload-time = "2026-07-18T03:38:42.144Z" }, + { url = "https://files.pythonhosted.org/packages/98/51/a1155945bff7b91381875022ac1522c5dfdac0d006be8e7df389b3134eae/matplotlib-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c90be0b73568da4f662afac580956a76e308437e641b4a45aa08925eeb67d95f", size = 10927962, upload-time = "2026-07-18T03:38:44.302Z" }, + { url = "https://files.pythonhosted.org/packages/0d/3a/3d5e1f42dc761bf53401a62a83ff93389b37de9d2c093b2a3aa49ac34f1b/matplotlib-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:68408341f2312836fbbdf6b3c78047f65b2d8752f5fd221c3e72d348f5b34f8b", size = 9334074, upload-time = "2026-07-18T03:38:46.616Z" }, + { url = "https://files.pythonhosted.org/packages/e2/db/3f5ea5a5b64060ef5e1ff60a19170423e41ce21b8497a6fe15a36e0b43e3/matplotlib-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c1f44890d435c1b4ef52f701ad5828cb450ea97bcc83918fda6be74965d6cd2", size = 9007662, upload-time = "2026-07-18T03:38:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/c7ae5e0531425b69c0826b00ebbc264c85cab853f1cd6e096c9983c2cdc1/matplotlib-3.11.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5e510088c27a89d53580a752f959146893563e63c330e161d159b0fee652af6f", size = 9503790, upload-time = "2026-07-18T03:38:51.527Z" }, + { url = "https://files.pythonhosted.org/packages/92/79/15be162e0a2ed546939674e2e97d0e33ec2447d86d4d4e611fa295bb178c/matplotlib-3.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:1524e2bdd48a93557aa47ddcfe9c225dfdd57d5a01a5c49128c20f0632980ee1", size = 9336148, upload-time = "2026-07-18T03:38:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7f/36ffe144fc4aacfe0e3ed2318f72b6755d1e73b041d619b4d393e60f5a66/matplotlib-3.11.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:11664c551345553db92e61cae6cf1376f138f8c47cafdf13b64b18f3e3e9e464", size = 10049244, upload-time = "2026-07-18T03:38:55.911Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/55812d68c0a840d3a463638f48c00ab1fe338518ec49a640cb6473b444af/matplotlib-3.11.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e1f8922ba31959cf6a9dfb51be64b7f7bc582801a3957dc0c2f3afcd3537adf", size = 10860798, upload-time = "2026-07-18T03:38:58.282Z" }, + { url = "https://files.pythonhosted.org/packages/7a/64/cca444b4eb5e6c768c44fc5e1f0b5211f20ca2b282778051996e996a2bdf/matplotlib-3.11.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83235693abde86e5e0129998f80ee39fc7f58e6d56a88fafb28a9278833e9d5f", size = 10943282, upload-time = "2026-07-18T03:39:00.465Z" }, + { url = "https://files.pythonhosted.org/packages/e5/0f/a49c329d394f2e9ef38506982107e8b04ecf94dd41a9d8423ff82cc737c7/matplotlib-3.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9a076f4fc5cdc43fdf510f5981418d25c2db4973418d9f22d8bb3dc8045ada78", size = 9383532, upload-time = "2026-07-18T03:39:02.468Z" }, + { url = "https://files.pythonhosted.org/packages/e4/50/103e86afb806d8f64d04ede14e4cfc09dbfc25f512421ff85fdd6ebd59cf/matplotlib-3.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:216fbb93a74add02ddb4cb38ef5348f59ac00b3e84567eaf16598772d40e150a", size = 9059665, upload-time = "2026-07-18T03:39:04.607Z" }, + { url = "https://files.pythonhosted.org/packages/35/04/3079499fa8cb661ea66d13d6439d5a3ae6710a7afd5c7f72e08914f275f8/matplotlib-3.11.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:30c492d4ba9448595b6fd8708c6725963f8148e25c0d8842948da5b05f0ee8d3", size = 9456022, upload-time = "2026-07-18T03:39:07.041Z" }, + { url = "https://files.pythonhosted.org/packages/53/a2/69acfe84ec1f32930e801a5782a07fc5c79c8c6599a507b806d859d5da8e/matplotlib-3.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ac104be2768ffdd8655db9e71b768cbb45f2b9aa7b450cf1595e8f65d3822319", size = 9285475, upload-time = "2026-07-18T03:39:09.562Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b3/31b15a2ca56d4ddd6aaa1c884c2f51cf9a61cfaf5ca6f6fbd6343d38e6df/matplotlib-3.11.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be943cb68bc6660ead58c55b3aa6366cba2ef7feb06460fbcce32360376f19f", size = 10847102, upload-time = "2026-07-18T03:39:11.532Z" }, + { url = "https://files.pythonhosted.org/packages/64/0d/a17e966e620545c1548125af0b29ac812dd17b197a18a7462ac12fa859ee/matplotlib-3.11.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5af0dcda57d471440a7b5b623e70e0a61003518443d9098f211a96ecfbbc25be", size = 11131087, upload-time = "2026-07-18T03:39:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/97/c5/5e100efdd67abb7de20befaa333612ef9bfc63417fb71398f904f25d083c/matplotlib-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3d3fd84082b1afbd9398466c81309e20045be20d48fe0fb18c43504d164cbbb2", size = 10929036, upload-time = "2026-07-18T03:39:16.888Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/d719a0a36930ecc8dfc801ff340f9dcfc4223f8ca5d39d06b4020032fff8/matplotlib-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:9601a1e90be21e4884c53b4f3dc3ee0544654946f9975258d691f1c2e2f119c6", size = 9489571, upload-time = "2026-07-18T03:39:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/48/65/facabdc2f1f6caba7e856db64dfedddca25f7608df07d96a1c8fd114fd3b/matplotlib-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:ae30c6109848ac0f9fa36c5d6270938487614c47ba31860bd5361266dabc5685", size = 9164486, upload-time = "2026-07-18T03:39:21.424Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/18da6cd01cf96354534f98c468a25380c68ce582a2c9dd0cae12b04af4f2/matplotlib-3.11.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dadfe80797174e2984aae3be0b77594a3c72d2c0a40fbd4a0de48d2728caf3ae", size = 9504876, upload-time = "2026-07-18T03:39:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/b0/f0b63555a18b79d038c81fd6126f35fc4dfce0eaff48d96103348c7cf935/matplotlib-3.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:89b193b255f4f6f7948dbcee3691f4f341ab05d9a8874a67b45ddb4182922eda", size = 9336120, upload-time = "2026-07-18T03:39:25.797Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dd/f210ec7c4a6f198d5567237048a93d0811fb5a1f1691f13320e592f95b41/matplotlib-3.11.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191163532cdefcb1571ca38a6d7e6474baccde64495783e6ba47aa07ec4b9bbb", size = 10858033, upload-time = "2026-07-18T03:39:27.999Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/d6d5324507c5fbb316db48e258c09c2807f3de03d9af47017e120070926f/matplotlib-3.11.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fdf1c818ab05d0e74002091ddaf414478a3a449ec9d51c8976d45be7e3a01e2", size = 11141827, upload-time = "2026-07-18T03:39:30.092Z" }, + { url = "https://files.pythonhosted.org/packages/0f/68/3c22e9320bdce2c4d2f1320643ef706db7a24cb7420eea28b97a2d67f5a8/matplotlib-3.11.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b937b9dba5f5f6c1e31c47abe2186c865c0914fd18f2ce0dfc39c9adcef5951d", size = 10943061, upload-time = "2026-07-18T03:39:32.356Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4a/907ed190ee81a9df581e0ed5456134fc0f7cb55ffcfda2f9e54ca900761c/matplotlib-3.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f2912f647f3fbe1ccf085f91e213936f9101bead81a5e670565b1f1b3712f4fb", size = 9540074, upload-time = "2026-07-18T03:39:34.789Z" }, + { url = "https://files.pythonhosted.org/packages/23/d4/97c19b77e0a6e3b48581185bb65088f431cd20186076cc0f650a1757ea46/matplotlib-3.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:54d47b8ae8b579633a3902ca5b4ad6c1e132a5626d64447b2e22a66394e79987", size = 9213472, upload-time = "2026-07-18T03:39:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/ee/38/ceb1d637c4db6d06141f3739e93af3321e7caaabe69b57ae48ffe3ee95b1/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:427258425f9a3fc4ed79a91f9e9b9aaf5a82cb6571e85dc14063cc6fbb993741", size = 9438045, upload-time = "2026-07-18T03:39:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/72ad8b58602d3a6ef1dfc4b65ecd01634ab65a2bdf494c9fe0e966dbf081/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ac697e591c11b6ad04679a73c2d2f9980fe9d9f0311fb414a2e329706343dfb", size = 9266127, upload-time = "2026-07-18T03:39:41.597Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/69552382fcc8e93d1f2763ef2665980a900a48b7f3a4c57ed290726d1cbc/matplotlib-3.11.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4b9ac2f1f607ecda2af90a5232beee2af7582fce1cc30c4b6a1b012dc21ee99", size = 10019439, upload-time = "2026-07-18T03:39:43.78Z" }, +] + +[[package]] +name = "mypy" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/af/4e516a05d3ca2eb9283e9ec45b2c02225c1514dd6da49fd3c9eaa6639370/mypy-2.3.0.tar.gz", hash = "sha256:465965d41cd9a2726694e983e8ce7113259327bec798115d1e1dfa2a52fb666e", size = 3988104, upload-time = "2026-07-13T11:34:53.387Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/b9/d75b3082b05f1b3028828aeb18e74ae5ab0a0936051bbf1f32f59f654747/mypy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3419d00717afbc5265b50dd14b1278f29ea4884dd398ab67873489ac093fd329", size = 14838725, upload-time = "2026-07-13T11:32:44.655Z" }, + { url = "https://files.pythonhosted.org/packages/a9/50/79a65c6ea6e115bc73296038a4543b2d5c91f07912b918a2c616a2514bba/mypy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfca8ee88544090f86b6dcce05ec55d66eb48a762412ac2507810ba4bd793b6f", size = 13911128, upload-time = "2026-07-13T11:32:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/90/48/e11ed7716c26953ca321f726e452e374dbf81a6f2b8b212ec02af29b6b8f/mypy-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75cbb4b9ef04a0c84a957f07abc4504fbf64b8dcc145675101f2d3a78a4b1d6a", size = 14146742, upload-time = "2026-07-13T11:33:03.313Z" }, + { url = "https://files.pythonhosted.org/packages/06/72/6807565b1c4861ef66f7fdd98b51c61556356eab80235717b46c53bb8627/mypy-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:982e3d53dd23d0a4cef67dd66791fdbede0cf38f9eb617bf47663554c51e1e36", size = 15081418, upload-time = "2026-07-13T11:31:13.899Z" }, + { url = "https://files.pythonhosted.org/packages/00/80/1ea14c5d80e589e415973db3e47c78c2219a305b808b2b506395342c1d79/mypy-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85c5385b93012ffa3b31479ab579aef5415f4f3a32c6cf1ae07a984d2a0ff461", size = 15328164, upload-time = "2026-07-13T11:31:35.723Z" }, + { url = "https://files.pythonhosted.org/packages/37/28/8223157404a3d51920078459c37f80fbdc590e1d8ea049dc5ce48643022a/mypy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:13b1b16e2fa39f3b2e33fb1c468abc7a69369fa2e886b4b87b5afc81472325cd", size = 11136472, upload-time = "2026-07-13T11:27:37.018Z" }, + { url = "https://files.pythonhosted.org/packages/6f/cc/ea27e5959c5f258585a756b252031f3b313583d81b5064b2bebc41d3706b/mypy-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5cd2f027a972a4a5f2278a11fac9747f5f81a53a30b714d74950b6807e55568", size = 10135800, upload-time = "2026-07-13T11:30:08.92Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/0e7e592619e2133596a47cdd642534b0456545c218430bd3b9d8fefdd1b1/mypy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d53fc67b9d28a43c6199077f49fea0f05839e36cf6158500331c9549225e5a5", size = 15026523, upload-time = "2026-07-13T11:34:49.206Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/1e1731df090a857df2807177a4626863e5ac0f0256513c35780efe53986f/mypy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fbc00cee7bdbb9291979ddc9d08034a29dfcda4932628c9bbc28c1edd589df0c", size = 14032189, upload-time = "2026-07-13T11:33:57.168Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/cab921f4a806e171f34113e6181dd23c55358ccf6a80741269ef594a410e/mypy-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04e617030eca5221909c8b7d8d7fd1c637948199aa2100b2ad9813feb07e1491", size = 14198696, upload-time = "2026-07-13T11:32:12.767Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/e6d008bb19fe446e3662d85e0e2717bf9f2d611a2164fb29d6e067dbf46c/mypy-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56c184d2c20ca6b6378d58d1960270a767f41f5e44acbbd27f05effef4f4e1d7", size = 15286904, upload-time = "2026-07-13T11:34:27.594Z" }, + { url = "https://files.pythonhosted.org/packages/db/83/94397c9293608a364aa03e8084fb34ede4ae976a260384b9b52929308135/mypy-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3961a4a34b05f7c74b0f05aa51fbfe99a2d1e126038df40318d15c8f558b7ef3", size = 15528342, upload-time = "2026-07-13T11:34:07.819Z" }, + { url = "https://files.pythonhosted.org/packages/cf/96/d8b37d819adec6cfccfb1fd3afc1735d94717ddeafb45536db9c6943e09b/mypy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1942b9314d4c784b8ea1dbab4972603290e5dd5630f06675f13aec97526bc4c", size = 11218346, upload-time = "2026-07-13T11:28:27.745Z" }, + { url = "https://files.pythonhosted.org/packages/2b/cd/cd9f725b19b19e5b530a154cf9bcf9e94279c5d55b3c34fb42b3aa48ea1b/mypy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:be51653d7669d7d7955d613b8d0bb57d5b652eaf71a873ddf65ac87254dd2595", size = 10204525, upload-time = "2026-07-13T11:31:02.552Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ae/f7d056eb0294586a572d0d0d89580ec633c064db520f11d37d5a2fb833bd/mypy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:91ad22a52ae2c7e621c2f67c94d5a17f66b3209a4cff5cf8a573579835c69e97", size = 14947298, upload-time = "2026-07-13T11:27:47.734Z" }, + { url = "https://files.pythonhosted.org/packages/32/d5/db3e7af01e7844d21662c6ddc1f7825ec7cb4053f0391ac02faf3638396f/mypy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99ac767cc5d3b64c8d0ae226ead10c96694f94e4e7da1668642225dcd4e75aac", size = 13950768, upload-time = "2026-07-13T11:27:57.726Z" }, + { url = "https://files.pythonhosted.org/packages/d9/fb/43c031f0190513d1ec248ed037eceb742ddd2a4d74bbf406658a28173837/mypy-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6d2c484742a4d7b0ed6d07b143375624d3b899c5749c7b3c947f56261f48a6", size = 14151586, upload-time = "2026-07-13T11:29:18.615Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c3/f8b2ffc60883084da91be51af58e88a7ffd4ff9795acb7d902ff88d31eb1/mypy-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7da939dd335cfd2ad788bdfd081c9f4e47634ab995e5a45eb15fd1e5bc052f8b", size = 15227411, upload-time = "2026-07-13T11:30:29.904Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/16b917fc7adcf03f1aadddfc93aab804ffb234b1ab09c0ffd6d92a5d34a2/mypy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7247eb2824f996722a949530183394921ca71deb9680052a338cf53cff7925c2", size = 15478790, upload-time = "2026-07-13T11:33:14.686Z" }, + { url = "https://files.pythonhosted.org/packages/c0/88/aaa65a93c73d0cdae7e42f8adb302bf6885bb281302084f99d0290a35347/mypy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:75b0984bb3cbd76bb5c9291a8671f7ae66ca3b51c7584c358fc2e923259f0757", size = 11234919, upload-time = "2026-07-13T11:33:39.28Z" }, + { url = "https://files.pythonhosted.org/packages/35/19/b40de63f1a80e63bc2d40f0679a6a8dbd34e95176c8122119bdf406aa552/mypy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:d78fcf900b59cb7e82cb7e3a235e31b462d9333d92285bd1e4952d355b8ffba1", size = 10201510, upload-time = "2026-07-13T11:31:52.619Z" }, + { url = "https://files.pythonhosted.org/packages/a4/58/fa0ae047da911f540284009b4f44b96fe09d83c076d7c103e9d645f46303/mypy-2.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea317b060ce83e26050f8f9e4d7d6bf44ed7597c8ff9990bccffbb9d1d8522db", size = 14941909, upload-time = "2026-07-13T11:32:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/15/14/2ba1d61452d7c2a7fe12741e8d374e52b183476b07aa7f9e2a0d02b0720a/mypy-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:094af99f92638aa92852326188b85a89e50f4a472f44827c03362228482f0762", size = 13967581, upload-time = "2026-07-13T11:30:00.587Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5a/483fb9e5ffbbb1a28dccc7b0a13d141b17ac769b6c9f488c0a0c63698962/mypy-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de121747278144fc9ae7caa2e978cf5df12aebc82933182f5b3b86081a30baef", size = 14168807, upload-time = "2026-07-13T11:28:48.6Z" }, + { url = "https://files.pythonhosted.org/packages/ae/77/70d7a10732063beb74ad713682cf871e88f5c5fa39bfc8beff8a524bf9cb/mypy-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37fa4de896a84e2dc9200d91e614c22563b43d1a266789d4bbac7b22ebe6192b", size = 15200144, upload-time = "2026-07-13T11:31:25.283Z" }, + { url = "https://files.pythonhosted.org/packages/56/72/766218ac783be4fdfcd699b90037b63017348a3e86fb2c1fbfb18302637d/mypy-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f1b3a98dfd21058bc759bb3337d5d1f61d0fdf9f3cf9c00f4291790fb5427bff", size = 15460389, upload-time = "2026-07-13T11:29:29.077Z" }, + { url = "https://files.pythonhosted.org/packages/38/4e/8a9db7411ecb8ec0cb1fd05dba432f28bafffcd38b4e887714a4a0506689/mypy-2.3.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:944c665d984157cb96a679dfb7a4a81dd1d36b24b9c284b699514e6e626b82d4", size = 7753664, upload-time = "2026-07-13T11:29:08.147Z" }, + { url = "https://files.pythonhosted.org/packages/65/4c/c3f8bfd6ed0e5e38b5a244403b27f821d433443df5a15a278417c10a3a3c/mypy-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:4359424140d985192c778c1ce2c114a10c1ca58a381ed79cfa70d37df94b299f", size = 11417237, upload-time = "2026-07-13T11:33:47.467Z" }, + { url = "https://files.pythonhosted.org/packages/3c/00/89a32eaf5ccf174bc4f90db0eaea5d70636c01b8d49f384bdab2e8834390/mypy-2.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:3dd0bed92c4bdec57c42505b96416fb9e6a5aa7be84d2809bcd5f2ecec2860d7", size = 10389252, upload-time = "2026-07-13T11:31:43.81Z" }, + { url = "https://files.pythonhosted.org/packages/31/56/104f93d69aa9f339b6b9d3b0a7faa699b8b466c942cf3ae86cc2a2ec0915/mypy-2.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:691fdc37132b1ae628d834f672e74de83462d9fb4aff621835767fb43a8dd373", size = 16385495, upload-time = "2026-07-13T11:29:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/d2/03/f1d2123313f55efafdd27706960f43a771c62f1b68426c76043f3ab9ebf3/mypy-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:aec15d465d477558fd842757b487849007311cf3897849cdda0e3162ac0ac556", size = 15098155, upload-time = "2026-07-13T11:30:40.301Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5d/d5f9200399b445e81726c4f23becee33f233aee81c72680b1ef3a258b641/mypy-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b352b7e49f5e6576009e8df730e1ff4f915cb565b851b396d2ffe2f5a6f5da88", size = 15514155, upload-time = "2026-07-13T11:34:38.569Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ce/69977c555f08faa3190cfde44189b89dbd56861b1ab97aa18fc5f3a2e4a3/mypy-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c6c6bf687b17f90dbfcad95b960d32eaa0154c00da45f03ab50bf8952e047fe", size = 16766351, upload-time = "2026-07-13T11:33:29.195Z" }, + { url = "https://files.pythonhosted.org/packages/bc/92/6648b6caa3ab9e00f9ac0c2a78307805f873dd48139b24a6f6f7c3667bbf/mypy-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f4ed18f111bfe2d599bca7468e7f9251042c1c2118f762c8de2766a56d773c60", size = 17043490, upload-time = "2026-07-13T11:30:53.927Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ab/0dc91d80f3f016634c68d451f294a97320fe903a9b6f90b9e57b3f7f1717/mypy-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0b025a93cffb9781d231f232be07a17912f35f10a313c24f301c81e842870654", size = 12146869, upload-time = "2026-07-13T11:29:38.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/b5/4c964d02634ba81f4d1c84838e5c5b18ab06d13ed568960f5d6318495ccc/mypy-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:adebc76aab4f3495a88b41d48aa4aff0c03f2822501da76625afcca5975f19e5", size = 10965113, upload-time = "2026-07-13T11:28:07.056Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fa/fdc54fe583ba3cafbcedfb70eeeaf03849f75b1827a07096c7bd996f582d/mypy-2.3.0-py3-none-any.whl", hash = "sha256:6b1cdb579446b60432432b2b2403a6201b4b475a004d7f488511c9ba177c9e88", size = 2753292, upload-time = "2026-07-13T11:33:18.48Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "numdifftools" +version = "0.9.42" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/94/7c83677dddc78f93230f4c8a6a5faa06c92699a76eb019386a3aed8a9762/numdifftools-0.9.42.tar.gz", hash = "sha256:866675171f293c4bf2f1e1c5bf9b88a07d5396903e3b3e7fcc3879e2a01cfbc1", size = 79612, upload-time = "2025-12-11T12:06:10.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/60/c7504633e9fe53dc823db5d83dc15651c595cf35d668781832b4d2748272/numdifftools-0.9.42-py3-none-any.whl", hash = "sha256:22f926b8de24aa44f740bc26744a74555c1c80413068b1dee2edb3c3d10566f6", size = 94252, upload-time = "2025-12-11T12:06:09.322Z" }, +] + +[[package]] +name = "numpy" +version = "1.26.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" }, + { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" }, + { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005, upload-time = "2024-02-05T23:53:15.637Z" }, + { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297, upload-time = "2024-02-05T23:53:42.16Z" }, + { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567, upload-time = "2024-02-05T23:54:11.696Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812, upload-time = "2024-02-05T23:54:26.453Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913, upload-time = "2024-02-05T23:54:53.933Z" }, + { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, + { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, +] + +[[package]] +name = "opt-einsum" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/b9/2ac072041e899a52f20cf9510850ff58295003aa75525e58343591b0cbfb/opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac", size = 63004, upload-time = "2024-09-26T14:33:24.483Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd", size = 71932, upload-time = "2024-09-26T14:33:23.039Z" }, +] + +[[package]] +name = "oqupy" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h5py" }, + { name = "matplotlib" }, + { name = "numdifftools" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "tensornetwork" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/50/e65a13a051697dacfaa4106c51b2473b7c1e23133b174406db31c50e8c91/oqupy-0.5.0.tar.gz", hash = "sha256:cda14c4cb07a79a6efccb20e7847f8002767c5c03c9a1146ad5435bebd6d1cab", size = 127010, upload-time = "2024-06-24T13:43:09.625Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/6d/52f562416eb5045e0aa6b76343594b204bdcb994d9aa55630b4490e3caea/oqupy-0.5.0-py3-none-any.whl", hash = "sha256:5cb04003578cabbb7df7d6dd4dda12505d358d46eddfdc2593234c3d2f3054dc", size = 104902, upload-time = "2024-06-24T13:43:08.411Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "ruff" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/94/1e5e4967626faf12fa56999cd6222dff6992ceb086ad7945756baf70c7a7/ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982", size = 4790557, upload-time = "2026-07-23T19:11:30.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/81/1c8818fee7ce1a04cd7d1b3172e0a8f8e4f1dc4feb7fc390e16daa8af323/ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e", size = 10754633, upload-time = "2026-07-23T19:10:46.415Z" }, + { url = "https://files.pythonhosted.org/packages/23/df/beaf59c09d68db84304d555f188b276a77132a5d5b0b67a5c762aa143628/ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522", size = 10969164, upload-time = "2026-07-23T19:10:50.271Z" }, + { url = "https://files.pythonhosted.org/packages/42/ce/741cd197496a1abbf51352710fd15ed995d2a2be87189c1da26a450d6e83/ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81", size = 10488846, upload-time = "2026-07-23T19:10:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/52/2a/a2db8e88cade358f5cdcb05674a917751074109315d014eb6352d9a893f7/ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9", size = 10889729, upload-time = "2026-07-23T19:10:54.89Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/62a771694ebd63029dc953e27dbad40e1588bd4860ff9fe881018fddaa49/ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a", size = 10568275, upload-time = "2026-07-23T19:10:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e2/ced249fe8af5f086c5c58cc21cc3356d50f32f7401c5df87050c999620a7/ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717", size = 11385112, upload-time = "2026-07-23T19:10:59.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/0b/05154977a8fd69eeb6c103271f55403bfd8711f5c0f8ed07489d95a504e7/ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17", size = 12207008, upload-time = "2026-07-23T19:11:02.154Z" }, + { url = "https://files.pythonhosted.org/packages/fb/29/98225831a3a1eab0e02f4acc6ca6559a98611dcc68b6965ff4b7234627c1/ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4", size = 11650842, upload-time = "2026-07-23T19:11:04.557Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/6bd3cf90500653d55dc0ffc8507aa8300bd49d0214b2e8cb4d3fef2943ba/ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b", size = 11400718, upload-time = "2026-07-23T19:11:09.233Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a2/a54eb4eae05d66364050a5d3b8a9c5ef88196531b3cbe7109d873f87f819/ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0", size = 11426177, upload-time = "2026-07-23T19:11:11.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/16e3eea4b2a478a496919f5e36f17c4559e54620bd3bbac5d6affa068006/ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213", size = 10856126, upload-time = "2026-07-23T19:11:14.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/252eb8b868a16eec7257c14f504f77537e734b2d69c762e639e588e304a3/ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af", size = 10571208, upload-time = "2026-07-23T19:11:16.378Z" }, + { url = "https://files.pythonhosted.org/packages/21/09/817a482f542f7570cbb4554b26e896610c7114f539b1d9e2d2145bf6bef6/ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09", size = 11063329, upload-time = "2026-07-23T19:11:19.173Z" }, + { url = "https://files.pythonhosted.org/packages/2e/23/9403c180ca1cb9b1f7335f5c3e5305c09d49ea5b345196682a36028bde4a/ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed", size = 11489751, upload-time = "2026-07-23T19:11:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/1b2ef7bcde851c78d7f17f1cca13fd6dc695fc4b3d6197941e72cae5b132/ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb", size = 10785885, upload-time = "2026-07-23T19:11:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a3/d5e4ef7a56be3f928ffb90b94c25ba7d3cb9c7fe0736aeaaedf361770712/ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472", size = 11923141, upload-time = "2026-07-23T19:11:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9a/8415f2657cbe200f41a4531ccededf135505a92d4a012229121f885b26f9/ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d", size = 11273407, upload-time = "2026-07-23T19:11:28.705Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "tensornetwork" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "graphviz" }, + { name = "h5py" }, + { name = "numpy" }, + { name = "opt-einsum" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/87/2e042a26284d76e71903f014b4148f1572a28d58af9ae0c7e86924c0ea93/tensornetwork-0.4.3.tar.gz", hash = "sha256:c23b1089327d1fd7b6038b15f597a4c3dad31a7231c628039dc6d99a1e499da8", size = 173862, upload-time = "2020-11-13T20:11:04.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/7f/5a5ecca306b61734669794bf43a560def309fdcf721150ea57a61b0b0085/tensornetwork-0.4.3-py3-none-any.whl", hash = "sha256:b1679e84c46947e55610b3bf73adb45f90a23a9621c21cf711e044c41dee446c", size = 372351, upload-time = "2020-11-13T20:11:03.393Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] diff --git a/tracks/mps/solutions/Ranger-123/validation/ARTIFACT_PROVENANCE.json b/tracks/mps/solutions/Ranger-123/validation/ARTIFACT_PROVENANCE.json new file mode 100644 index 000000000..7f5afed3c --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/validation/ARTIFACT_PROVENANCE.json @@ -0,0 +1,58 @@ +{ + "schema_version": 1, + "generated_date": "2026-07-30", + "source_snapshot_commit": "2a6d61b5028c7960d3808111c51663cb65893ca9", + "heat_valve_generated_date": "2026-07-30", + "heat_valve_source_snapshot_commit": "b42725363a01e2ca88b951c3d28026df8104aa40", + "uniform_tempo_revision": "b76a018c32e5415989761d902b1b0e95f1a337da", + "verification": { + "pytest": "110 passed", + "ruff": "passed", + "mypy": "passed", + "result_audit": "passed: 6 result files", + "paper_audit": "passed", + "julia_import_smoke": "passed: 2/2 environment tests", + "wheel_build": "passed", + "heat_valve_audit": "expected rejection: dark-channel gates not met" + }, + "sha256": { + "results/paper/n3_heat_manifest.json": "67e157c8bcf353873c3c9bc725ef7d92267f0074c04a11ca0c53c430ef0bf507", + "results/paper/error_map_manifest.json": "482182b661fa63d243a929771339d553d6b1588ce7f52fa088736dff24658230", + "results/paper/model_comparison_manifest.json": "30e5bc4bf4228c9da1f83cc15f85ac921788bd2c0d4b4923bc9374309a75cef8", + "results/validation/uniform_tempo_single_spin.json": "ffa32de8d9fdc64b191a1168ea4285639d7fcaae962d8701d1c0a3be789d8720", + "results/validation/uniform_tempo_oqupy_crosscheck.json": "7f0cb2840805180ddb157100fd1a6f3fea92178a9aa7e95813a3025c1b1940b7", + "figures/paper/n3_sector_heat.png": "0e37e61b6c74464fbd051239712617d67513602c6edcbaa95b0490fb80554828", + "figures/paper/n3_sector_heat.pdf": "af783a170eddbc79c155cd2ff4df100040dbbad73b0b9a0d8bee76e336387ae5", + "figures/paper/n3_odd_difference.png": "f048a37aed6b68c89eb42cf93fdd45bfbb62e34e5250d07aa01a93b4f8f7bc4b", + "figures/paper/n3_odd_difference.pdf": "4fe9f941b069475e2304f9b037a7da3ba59bf0121e270cbfb35fce39d86deae1", + "figures/paper/error_maps.png": "366055d2284c83a9e3c88fcf1ae53931f2af86254d3693266441592ea59eeb4b", + "figures/paper/error_maps.pdf": "9ea3153a169ffb090a1201eab6feae16e2aabac1aa670895b8e8cf9d0838ca3a", + "figures/paper/dark_diagnostics.png": "511a625e345768d88a51bc0fc28a02124dc83ac4cdde9af8bc929956a8bc764e", + "figures/paper/dark_diagnostics.pdf": "960b07cacf57529c06cc0f3db6d6c4968c7e6fd72453ec48c4d0687082d5b7d6", + "figures/paper/model_variants.png": "b19332ffaabc4cce65890df12a053814427ae3191a9a28c4482aa0a8e134091f", + "figures/paper/model_variants.pdf": "2e494978fd56104c83cf7e9e84f0b962a4ddfa3c413fccfbd3b6bf3d8adcbbaf", + "results/heat-valve/isolated_scan.json": "629a7fc00fad22912de6f2ed3ce0197b4aa467334791e1b7b9ff54baa3a9eb40", + "results/heat-valve/heat_valve_manifest.json": "d7bb70e257089a4b7f88ba278a85e5533f1ac3fb8e5a6a7f637e98c714693303", + "figures/heat-valve/heat_valve_hero.png": "fd203c4bab0f6a95da7b13139339f7e4a124c26a494e20d249ef1c77f6d56134", + "figures/heat-valve/heat_valve_hero.pdf": "ebc15ff891106b3592f41e3b1770a2dfae7d8b02533e1793d24a725f1a2a86d4", + "results/validation/fig3_transversal_wd1.json": "5747407d5c19494d826d1bc8a52cd29a654dc774dc2fc69dfa30d8c19ec5cd1e", + "results/validation/fig3_transversal_wd1p5.json": "531b1705c89a42bdb2fa7f8a707f99f42e162ee9c72958f15e2acb806fa15464", + "results/validation/fig3_transversal_wd2.json": "a45949df0bde3cf7688cbd7863035950203f0aace7ea790975f40ebb5e0a4494", + "validation/fig3_transversal_summary.json": "5aef6a8c161dd18d8c434c33544786589bb8c4b4344d5d0e54c5405f2b68d9e3", + "validation/fig3_transversal_wd2.json": "3bac6b1566f7f97b74132797b2bf64cecf40debd480fc5d4e1f5de3688948fbf", + "figures/validation/fig3_transversal_summary.pdf": "b61fff7fcfb3743720891a0d85614039c280438d1c00507d45cbe29dedce3c15", + "figures/validation/fig3_transversal_summary.png": "c51e6280b5e48a332753174ef2d74c1c5f6e2948f49f9ffbb0604bdf6d8347ef", + "validation/n3_same_model_error_map.json": "a9588ee808b66f984e45f724e7f201dfd9e617c611a68068a8452c4df6158320", + "figures/paper/n3_error_maps.pdf": "958ecd31b08da93d168942ef7fc61e7d22ac21ecc7a8fed6aae9b045af622dd7", + "figures/paper/n3_error_maps.png": "07c5d5444285c07a0c2a47c4d988702189a18bd8cca603191cb0b4515c2c883c", + "results/paper/n4_even_j0.25.json": "cdd9d5c2694d788e807568f101be31924b2d829c83fcab7bd58e58ad00516cb5", + "results/paper/n4_odd_j0.25.json": "e7f97894653ac16e9ec937e61e332862d05684a454b9adc01798b83d9f713096", + "validation/n4_pilot.json": "b32fa3f65c4128b46d827272450c83b7547a16a951600f40aa7890f70a53cba6", + "figures/paper/n4_even_j0p25_comparison.pdf": "a616fb969ba5c3493cec393f18794670b121b8567b34901766cc8fd06b09cccd", + "figures/paper/n4_even_j0p25_comparison.png": "b09f1efd0997a4de43f7ec3994e4b4f9250c323dfd29e5abc39c204d322a3013", + "figures/paper/n4_odd_j0p25_comparison.pdf": "f92914262b4b9acc7ef96b6e65e2dc19e10338f612c9b87fe3210fe626c3b529", + "figures/paper/n4_odd_j0p25_comparison.png": "e6a59cf9d95593b8ce5d2637b01909215f39caa72502acdb206cb639decaa391", + "report/data-summary.txt": "680d05b2243e07038dcca60cef09d2264ba15284fb108fe2fefc8210bb6c542e", + "output/pdf/Ranger-123-technical-report.pdf": "69447a7643c9e145e9507601af4af48c77bc0c1c9e93bf0ceafd6709a60035b8" + } +} diff --git a/tracks/mps/solutions/Ranger-123/validation/fig3_transversal_summary.json b/tracks/mps/solutions/Ranger-123/validation/fig3_transversal_summary.json new file mode 100644 index 000000000..c97b8e175 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/validation/fig3_transversal_summary.json @@ -0,0 +1,41 @@ +{ + "method": "uniform_tempo_fig3_transversal_reproduction", + "complete": true, + "converged_points": 3, + "scope": "independent N=1 reproduction of all three published Fig. 3 bottom-panel curves", + "reference": { + "record": "https://zenodo.org/records/19593671", + "archive_md5": "0f3f9d9d8538aa96aee089973df7d9c2", + "panel": "Fig. 3 bottom" + }, + "points": [ + { + "drive_frequency": 1.0, + "converged": true, + "controls": {"steps_per_period": 120, "tolerance": 1e-7, "phase_samples": 15, "delay_periods": 12}, + "diagnostics": {"bond_dimension": 26, "connected_tail_amplitude": 0.001627256392104337, "fixed_point_residual": 0.00008087148135240392, "hermiticity_error": 0.0036099263191221318, "trace_error": 0.0000749222438648638, "minimum_density_eigenvalue": 0.2358702724786363}, + "metrics": {"continuous_relative_l1": 0.06333707191194522, "normalized_shape_relative_l1": 0.056243489156938865, "integrated_magnitude_ratio": 1.026771197996517} + }, + { + "drive_frequency": 1.5, + "converged": true, + "controls": {"steps_per_period": 80, "tolerance": 1e-6, "phase_samples": 4, "delay_periods": 12}, + "diagnostics": {"bond_dimension": 14, "connected_tail_amplitude": 0.0023604904459384114, "fixed_point_residual": 0.0005313576529202555, "hermiticity_error": 0.004272614695140364, "trace_error": 0.0003929538774845975, "minimum_density_eigenvalue": 0.15004045531920804}, + "metrics": {"continuous_relative_l1": 0.23826748531590006, "normalized_shape_relative_l1": 0.2735649534808351, "integrated_magnitude_ratio": 1.1622298099461896} + }, + { + "drive_frequency": 2.0, + "converged": true, + "controls": {"steps_per_period": 60, "tolerance": 1e-6, "phase_samples": 4, "delay_periods": 12}, + "diagnostics": {"bond_dimension": 14, "connected_tail_amplitude": 0.015605745626824328, "fixed_point_residual": 0.0003084472032725321, "hermiticity_error": 0.00320077988078783, "trace_error": 0.00022911934882153764, "minimum_density_eigenvalue": 0.05449589091447455}, + "metrics": {"continuous_relative_l1": 0.3455071050704775, "normalized_shape_relative_l1": 0.37183146692817987, "integrated_magnitude_ratio": 1.3118951143348248} + } + ], + "artifact_sha256": { + "results/validation/fig3_transversal_wd1.json": "5747407d5c19494d826d1bc8a52cd29a654dc774dc2fc69dfa30d8c19ec5cd1e", + "results/validation/fig3_transversal_wd1p5.json": "531b1705c89a42bdb2fa7f8a707f99f42e162ee9c72958f15e2acb806fa15464", + "results/validation/fig3_transversal_wd2.json": "a45949df0bde3cf7688cbd7863035950203f0aace7ea790975f40ebb5e0a4494", + "figures/validation/fig3_transversal_summary.pdf": "b61fff7fcfb3743720891a0d85614039c280438d1c00507d45cbe29dedce3c15", + "figures/validation/fig3_transversal_summary.png": "c51e6280b5e48a332753174ef2d74c1c5f6e2948f49f9ffbb0604bdf6d8347ef" + } +} diff --git a/tracks/mps/solutions/Ranger-123/validation/fig3_transversal_wd2.json b/tracks/mps/solutions/Ranger-123/validation/fig3_transversal_wd2.json new file mode 100644 index 000000000..f8e1a5a22 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/validation/fig3_transversal_wd2.json @@ -0,0 +1,39 @@ +{ + "method": "uniform_tempo_fig3_transversal_reproduction", + "complete": true, + "scope": "independent N=1 reproduction of the published Fig. 3 bottom-panel curve at drive frequency 2.0", + "reference": { + "record": "https://zenodo.org/records/19593671", + "archive_md5": "0f3f9d9d8538aa96aee089973df7d9c2", + "panel": "Fig. 3 bottom" + }, + "controls": { + "steps_per_period": 60, + "delay_periods": 12, + "phase_samples": 4, + "tolerance": 1e-6 + }, + "diagnostics": { + "bond_dimension": 14, + "connected_tail_amplitude": 0.015605745626824328, + "fixed_point_residual": 0.0003084472032725321, + "hermiticity_error": 0.00320077988078783, + "trace_error": 0.00022911934882153764, + "minimum_density_eigenvalue": 0.05449589091447455, + "process_tensor_cache_hit": 1 + }, + "comparison": { + "continuous_relative_l1": 0.3455071050704775, + "normalized_shape_relative_l1": 0.37183146692817987, + "integrated_magnitude_ratio": 1.3118951143348248 + }, + "delta_peaks": [ + {"frequency": 2.0, "harmonic": 1, "weight": 0.028133718980199732}, + {"frequency": 4.0, "harmonic": 2, "weight": 3.5293284163875763e-10} + ], + "artifact_sha256": { + "results/validation/fig3_transversal_wd2.json": "a45949df0bde3cf7688cbd7863035950203f0aace7ea790975f40ebb5e0a4494", + "figures/validation/fig3_transversal_wd2.pdf": "3ba0bf730d41e3da1d37fde8e35f7ca7eb2c0cc0c2aa6fc2379e939be51e04d7", + "figures/validation/fig3_transversal_wd2.png": "a93859b38040c02a90a177af9ada81a06b905481302d4d385aa6d046482f381f" + } +} diff --git a/tracks/mps/solutions/Ranger-123/validation/n3_same_model_error_map.json b/tracks/mps/solutions/Ranger-123/validation/n3_same_model_error_map.json new file mode 100644 index 000000000..d80b87f96 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/validation/n3_same_model_error_map.json @@ -0,0 +1,19 @@ +{ + "method": "n3_uniform_tempo_vs_floquet_markov_qr", + "complete": true, + "converged_points": 6, + "model_scope": "same N=3 reflection sector, Hamiltonian, drive and bath parameters", + "points": [ + {"j": 0.25, "sector": "even", "trace_distance": 0.9996997375851921, "correlation_error": 1.1529657847337855, "heat_error": 9.016706811174881}, + {"j": 0.50, "sector": "even", "trace_distance": 0.9999010667013211, "correlation_error": 1.0341250650150768, "heat_error": 11.715780183401042}, + {"j": 1.00, "sector": "even", "trace_distance": 0.9920891824177340, "correlation_error": 1.0243258716943124, "heat_error": 9.077234824882260}, + {"j": 0.25, "sector": "odd", "trace_distance": 0.4752509683642790, "correlation_error": 0.5666192029309954, "heat_error": 0.3920114223680808}, + {"j": 0.50, "sector": "odd", "trace_distance": 0.4752509683642792, "correlation_error": 0.5666192029309957, "heat_error": 0.3920114223680807}, + {"j": 1.00, "sector": "odd", "trace_distance": 0.4752509683642792, "correlation_error": 0.5666192029309958, "heat_error": 0.3920114223680807} + ], + "artifact_sha256": { + "results/paper/n3_error_map_manifest.json": "287f9cb189a57f0348791ce194adf75bccb5628dbe4975baf99b2f43597770ad", + "figures/paper/n3_error_maps.pdf": "958ecd31b08da93d168942ef7fc61e7d22ac21ecc7a8fed6aae9b045af622dd7", + "figures/paper/n3_error_maps.png": "07c5d5444285c07a0c2a47c4d988702189a18bd8cca603191cb0b4515c2c883c" + } +} diff --git a/tracks/mps/solutions/Ranger-123/validation/n4_pilot.json b/tracks/mps/solutions/Ranger-123/validation/n4_pilot.json new file mode 100644 index 000000000..a7e922af5 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/validation/n4_pilot.json @@ -0,0 +1,49 @@ +{ + "method": "n4_uniform_tempo_convergence_pilot", + "complete": true, + "converged_points": 2, + "model_scope": "same N=4 Hamiltonian and bath at J/Omega=0.25, resolved by reflection sector", + "points": [ + { + "sector": "even", + "dimension": 10, + "delay_periods": 6, + "bond_dimension": 13, + "connected_tail_amplitude": 0.02133175984671692, + "fixed_point_residual": 0.00035886128826079347, + "hermiticity_error": 0.0008819593032858546, + "trace_error": 0.0003388881943872353, + "minimum_density_eigenvalue": -0.00046822521586976576, + "markov_metrics": { + "trace_distance_raw": 1.0008536447854561, + "correlation_error": 1.6286041909228848, + "heat_error": 5.493776886006749 + } + }, + { + "sector": "odd", + "dimension": 6, + "delay_periods": 3, + "bond_dimension": 8, + "connected_tail_amplitude": 0.036779095635906804, + "fixed_point_residual": 0.00037773511660346736, + "hermiticity_error": 0.00009174537985208175, + "trace_error": 0.0003542407259329822, + "minimum_density_eigenvalue": -0.003262969712857272, + "markov_metrics": { + "trace_distance_raw": 1.0043613609854345, + "correlation_error": 1.601339921831332, + "heat_error": 3.4047954293441243 + } + } + ], + "note": "Raw trace-norm diagnostics can exceed one slightly because the accepted UniformTEMPO density matrices retain small negative eigenvalues within the preregistered -0.005 gate.", + "artifact_sha256": { + "results/paper/n4_even_j0.25.json": "cdd9d5c2694d788e807568f101be31924b2d829c83fcab7bd58e58ad00516cb5", + "results/paper/n4_odd_j0.25.json": "e7f97894653ac16e9ec937e61e332862d05684a454b9adc01798b83d9f713096", + "figures/paper/n4_even_j0p25_comparison.pdf": "a616fb969ba5c3493cec393f18794670b121b8567b34901766cc8fd06b09cccd", + "figures/paper/n4_even_j0p25_comparison.png": "b09f1efd0997a4de43f7ec3994e4b4f9250c323dfd29e5abc39c204d322a3013", + "figures/paper/n4_odd_j0p25_comparison.pdf": "f92914262b4b9acc7ef96b6e65e2dc19e10338f612c9b87fe3210fe626c3b529", + "figures/paper/n4_odd_j0p25_comparison.png": "e6a59cf9d95593b8ce5d2637b01909215f39caa72502acdb206cb639decaa391" + } +} diff --git a/tracks/mps/solutions/Ranger-123/validation/uniform_tempo_oqupy_crosscheck.json b/tracks/mps/solutions/Ranger-123/validation/uniform_tempo_oqupy_crosscheck.json new file mode 100644 index 000000000..dc155588d --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/validation/uniform_tempo_oqupy_crosscheck.json @@ -0,0 +1,17 @@ +{ + "complete": true, + "converged": true, + "interpretation": "independent coarse-backend diagnostic; not a convergence refinement", + "method": "coarse_uniform_tempo_vs_oqupy_cross_validation", + "metrics": { + "connected_correlation_relative_l1": 0.28171563084762685, + "continuous_heat_relative_l1": 0.51707850109946, + "phase_state_frobenius": 0.373843235587285 + }, + "oqupy_fingerprint": "1639ce757970ef3819ad72ab1cd7d40b2c1a3527f5e53a6b6d57c2fff34e847e", + "projected_odd_j_invariance": { + "coupling_frobenius_residual": 0.0, + "h0_frobenius_residual": 0.0 + }, + "uniform_fingerprint": "97688130575f00aa3234cef5ca7abff20ab1214cd5a472355cb3f7bd78b7a5a4" +} \ No newline at end of file diff --git a/tracks/mps/solutions/Ranger-123/validation/uniform_tempo_single_spin.json b/tracks/mps/solutions/Ranger-123/validation/uniform_tempo_single_spin.json new file mode 100644 index 000000000..8dbdb4f19 --- /dev/null +++ b/tracks/mps/solutions/Ranger-123/validation/uniform_tempo_single_spin.json @@ -0,0 +1,884 @@ +{ + "bath": { + "alpha": 0.05, + "cutoff": 2.5, + "temperature": 0.0 + }, + "continuous": [ + 0.0, + 6.246011012345299e-07, + 2.410763291657597e-06, + 5.234328797497268e-06, + 8.980661160415742e-06, + 1.3544313806662395e-05, + 1.8828632121399976e-05, + 2.474529868847087e-05, + 3.121383461272567e-05, + 3.816107227429673e-05, + 4.552061634074449e-05, + 5.3232310327801245e-05, + 6.124172547315725e-05, + 6.949968726153122e-05, + 7.796185276194096e-05, + 8.658834919810844e-05, + 9.534348107795442e-05, + 0.00010419550996421254, + 0.00011311650776146267, + 0.00012208228137754667, + 0.00013107236389979477, + 0.00014007006507585866, + 0.00014906257193429427, + 0.0001580410888228165, + 0.0001670010049702479, + 0.00017594207688128747, + 0.00018486861245667883, + 0.00019378964372608447, + 0.0002027190755477618, + 0.00021167579865498395, + 0.00022068375711660235, + 0.00022977196272779308, + 0.00023897445213050202, + 0.00024833018660273376, + 0.0002578828993960251, + 0.00026768090108819783, + 0.00027777685939189, + 0.0002882275758477462, + 0.0002990937873694214, + 0.00031044002516536745, + 0.00032233456658811217, + 0.0003348495164372542, + 0.0003480610527466853, + 0.00036204986785957026, + 0.00037690182859772214, + 0.00039270886979465315, + 0.0004095701239096078, + 0.0004275932766976175, + 0.00044689612607343306, + 0.00046760830968146656, + 0.0004898731576963622, + 0.000513849622457748, + 0.0005397142369798788, + 0.0005676630611781005, + 0.0005979135883935308, + 0.0006307066054864546, + 0.0006663080267570033, + 0.0007050107538693615, + 0.000747136648714555, + 0.0007930387410017162, + 0.0008431038240458586, + 0.0008977556171160274, + 0.000957458687134105, + 0.001022723322999492, + 0.0010941115394219277, + 0.0011722443517982592, + 0.0012578104084528515, + 0.0013515759919694621, + 0.0014543963094495098, + 0.0015672278860644879, + 0.0016911417625596556, + 0.001827337082167944, + 0.0019771545435792706, + 0.0021420891027325735, + 0.0023238012359485995, + 0.0025241260385128112, + 0.0027450794333238326, + 0.0029888608089216756, + 0.0032578514979777606, + 0.003554608646137877, + 0.0038818542036702826, + 0.004242458991971475, + 0.0046394220435401505, + 0.005075845674398835, + 0.005554907006453562, + 0.006079826896525985, + 0.006653837430604904, + 0.007280149288463577, + 0.007961920359016139, + 0.008702226977356037, + 0.009504039051119701, + 0.010370200142555379, + 0.011303413275290448, + 0.012306232849570735, + 0.013381062591569914, + 0.014530158952447329, + 0.015755638838007992, + 0.017059490021459265, + 0.01844358210424675, + 0.019909675478874882, + 0.021459425447689518, + 0.02309437849448109, + 0.024815957717866585, + 0.02662543463581758, + 0.028523884969403633, + 0.030512126610282724, + 0.03259063875876238, + 0.034759462163744315, + 0.037018081467599676, + 0.03936529181276555, + 0.04179905304881195, + 0.04431633602888158, + 0.04691296653933202, + 0.04958347330254375, + 0.052320947169898636, + 0.05511691902636733, + 0.05796126401654035, + 0.06084213944393731, + 0.06374596307617525, + 0.06665743761042427, + 0.06955962573701086, + 0.07243407862258716, + 0.07526101877359367, + 0.07801957620653377, + 0.08068807472683338, + 0.08324436299441287, + 0.08566618302757315, + 0.08793156696321491, + 0.09001925134156058, + 0.09190909699851277, + 0.09358250189555437, + 0.09502279394479701, + 0.09621559112370455, + 0.09714911692560853, + 0.09781446044013849, + 0.09820577206044577, + 0.09832038790805815, + 0.09815887846856566, + 0.09772501954372242, + 0.09702568633854354, + 0.09607067420023278, + 0.094872452093565, + 0.093445857224106, + 0.09180774120638563, + 0.08997657973426544, + 0.08797205878025015, + 0.08581465088705342, + 0.08352519510073898, + 0.08112449353807687, + 0.07863293651418907, + 0.07607016663607512, + 0.0734547903693923, + 0.07080414340237419, + 0.06813411376600319, + 0.06545902423378377, + 0.06279157312872291, + 0.06014283041573984, + 0.05752228395115928, + 0.054937929079241055, + 0.05239639347281331, + 0.04990308825409039, + 0.04746237602338244, + 0.0450777464655552, + 0.04275199067274957, + 0.04048736617297138, + 0.03828574582658395, + 0.036148745172022, + 0.03407782438471032, + 0.032074362671401936, + 0.03013970456850261, + 0.028275179164932985, + 0.026482094654240597, + 0.024761711776094063, + 0.02311520058824644, + 0.021543585587731773, + 0.020047684463321065, + 0.01862804571636736, + 0.017284890056578375, + 0.01601805989957762, + 0.014826980512379866, + 0.013710635427307723, + 0.01266755773522004, + 0.011695837836988979, + 0.010793147237193124, + 0.009956777059483236, + 0.009183689194081784, + 0.008470577388920302, + 0.007813935189634262, + 0.007210127430158404, + 0.006655461972797193, + 0.006146258580707288, + 0.005678912153087279, + 0.00524994803240645, + 0.004856067666507089, + 0.00449418353612073, + 0.004161442899545035, + 0.003855240522247892, + 0.0035732211155493853, + 0.0033132726768626613, + 0.003073512283217107, + 0.002852266127077364, + 0.002648045694368223, + 0.0024595219728006565, + 0.002285499455095534, + 0.002124891483686128, + 0.001976698192759018, + 0.0018399879647930412, + 0.0017138829579565883, + 0.001597548903088936, + 0.0014901890375920684, + 0.0013910417579155814, + 0.001299381347372743, + 0.001214520981484704, + 0.0011358171331080653, + 0.00106267449305273, + 0.000994550582605109, + 0.0009309593520365432, + 0.0008714732203275623, + 0.0008157232004945066, + 0.0007633969557819144, + 0.0007142348286891056, + 0.0006680240629174083, + 0.0006245915858405593, + 0.0005837958270802037, + 0.0005455181118071931, + 0.000509654183748004, + 0.00047610638438916244, + 0.0004447769465754355, + 0.0004155627602590262, + 0.0003883518451691195, + 0.00036302163031462134, + 0.00033943900444167633, + 0.00031746197520494203, + 0.00029694266693248045, + 0.0002777313046381088, + 0.0002596807801969557, + 0.00024265137764888035, + 0.0002265152481332754, + 0.0002111602682808947, + 0.0001964929841924191, + 0.00018244042999277698, + 0.00016895070789036326, + 0.00015599231775973588, + 0.00014355232075598362, + 0.0001316335063469672, + 0.00012025079963577542, + 0.0001094271917698799, + 9.918949825548104e-05, + 8.956424770619113e-05, + 8.057397840287908e-05, + 7.223417513333661e-05, + 6.455101857490847e-05, + 5.752004937241045e-05, + 5.112577493528866e-05, + 4.534217473327122e-05, + 4.0133994971937194e-05, + 3.545867061808132e-05, + 3.1268675301135906e-05, + 2.7514079766677046e-05, + 2.414509795697805e-05, + 2.1114415648912646e-05, + 1.8379127776497666e-05, + 1.5902153881429813e-05, + 1.365305255057466e-05, + 1.160821073093934e-05, + 9.750437893802853e-06, + 8.068043851781972e-06, + 6.553518920850745e-06, + 5.2019632249287885e-06, + 4.009426487953308e-06, + 2.971320016066192e-06, + 2.0810492755028896e-06, + 1.3289901246175193e-06, + 7.018968824839849e-07, + 1.8278920251107496e-07, + -2.4867922989856276e-07, + -6.154101399343436e-07, + -9.416867209043845e-07, + -1.2517457687521384e-06, + -1.5682264763202102e-06, + -1.9106362244907943e-06, + -2.293971563464242e-06, + -2.7276189585202674e-06, + -3.2146361273416363e-06, + -3.751483031279505e-06, + -4.328234520155807e-06, + -4.929267336101747e-06, + -5.534375846017151e-06, + -6.120236543725711e-06, + -6.662113697021515e-06, + -7.135679616969436e-06, + -7.518814239190242e-06, + -7.793250584713019e-06, + -7.945944894230563e-06, + -7.970071705246924e-06, + -7.865573056088375e-06, + -7.639225005139216e-06, + -7.304221041871067e-06, + -6.879307911220924e-06, + -6.387542113219607e-06, + -5.854762439603197e-06, + -5.307893388222404e-06, + -4.773204813549458e-06, + -4.274654093869671e-06, + -3.832428533081928e-06, + -3.461788505312902e-06, + -3.1722874719828353e-06, + -2.9674154453741266e-06, + -2.8446800882510058e-06, + -2.7961069352961516e-06, + -2.8091096776415734e-06, + -2.867655327913706e-06, + -2.953629253437597e-06, + -3.048292876251104e-06, + -3.133723031945004e-06, + -3.19412663664551e-06, + -3.2169368670804377e-06, + -3.193616359200054e-06, + -3.12011734374322e-06, + -2.9969761657336124e-06, + -2.829048106688348e-06, + -2.6249156496848735e-06, + -2.396027244610756e-06, + -2.1556425140629426e-06, + -1.9176723674966236e-06, + -1.6955078749642935e-06, + -1.5009297688840276e-06, + -1.3431814348506852e-06, + -1.2282731081206876e-06, + -1.1585650264511285e-06, + -1.1326541851574782e-06, + -1.1455649486427197e-06, + -1.1892200206827555e-06, + -1.2531469903617537e-06, + -1.3253584231551742e-06, + -1.3933315133993586e-06, + -1.4450074306286894e-06, + -1.4697309948797156e-06, + -1.4590580096524046e-06, + -1.4073697819941927e-06, + -1.3122509962422335e-06, + -1.174606773575466e-06, + -9.985158321836042e-07, + -7.908375007426227e-07, + -5.606092905270369e-07, + -3.1828738416581947e-07, + -7.489358609634583e-08, + 1.5886175841486835e-07, + 3.7341098830728163e-07, + 5.610497130600683e-07, + 7.164920533038756e-07, + 8.372199705885996e-07, + 9.23603156470485e-07, + 9.78784465638975e-07, + 1.0083444187214016e-06, + 1.019775400614051e-06, + 1.0218105501853628e-06, + 1.0236629058586623e-06, + 1.0342363957985784e-06, + 1.0613713631006854e-06, + 1.1111835125557713e-06, + 1.1875468519034401e-06, + 1.291759111538605e-06, + 1.4224132832291594e-06, + 1.5754825287733385e-06, + 1.7446090751373544e-06, + 1.9215721328552543e-06, + 2.096896531138668e-06, + 2.2605536501127856e-06, + 2.4027000577264358e-06, + 2.5143974197989927e-06, + 2.5882598061354867e-06, + 2.6189811514391164e-06, + 2.6037057633254813e-06, + 2.5422175206331893e-06, + 2.4369377405892855e-06, + 2.2927364512581446e-06, + 2.116575848709978e-06, + 1.9170169540328347e-06, + 1.7036300267540895e-06, + 1.4863554420418504e-06, + 1.274864121381414e-06, + 1.0779651106404536e-06, + 9.03102756660979e-07, + 7.559776195583648e-07, + 6.403145233659729e-07, + 5.577888822905056e-07, + 5.081096583452525e-07, + 4.892450270079437e-07, + 4.97766012176329e-07, + 5.292748175392958e-07, + 5.788789572754247e-07, + 6.416699475921113e-07, + 7.131663699147851e-07, + 7.896853931353791e-07, + 8.686139344297933e-07, + 9.485598888763598e-07, + 1.029374509619914e-06, + 1.1120481397888362e-06, + 1.1984922107641867e-06, + 1.2912298391609135e-06 + ], + "controls": { + "auto_nc": true, + "cap_rank": 5000, + "delay_periods": 6, + "low_rank_svd": true, + "max_rank": 10000, + "memory_cutoff": 100000, + "phase_samples": 3, + "steps_per_period": 60, + "tolerance": 1e-06, + "truncation": "abs" + }, + "converged": true, + "delta_peaks": [ + { + "frequency": 1.0, + "harmonic": 1, + "weight": 0.025497244175075658 + } + ], + "diagnostics": { + "bond_dimension": 19, + "connected_tail_amplitude": 0.005549383080176303, + "dt": 0.10471975511965977, + "fixed_point_residual": 0.00014007807758301285, + "floquet_transfer_residual": 0.00014007807758162496, + "hermiticity_error": 0.0004168835562094166, + "julia_version": "1.12.6", + "manifest_sha256": "6038d836b15eeb9b2f1b20bf675fa3d464b6d7c21b534cd6df430e748bdda0f7", + "minimum_density_eigenvalue": 0.10636006886047145, + "period_steps": 60, + "phase_samples": 3, + "process_tensor_cache_hit": 1, + "tolerance": 1e-06, + "trace_error": 9.243986058611653e-05, + "uniform_tempo_revision": "b76a018c32e5415989761d902b1b0e95f1a337da" + }, + "frequency": [ + 0.0, + 0.0075, + 0.015, + 0.0225, + 0.03, + 0.0375, + 0.045, + 0.0525, + 0.06, + 0.0675, + 0.075, + 0.08249999999999999, + 0.09, + 0.0975, + 0.105, + 0.11249999999999999, + 0.12, + 0.1275, + 0.135, + 0.1425, + 0.15, + 0.1575, + 0.16499999999999998, + 0.1725, + 0.18, + 0.1875, + 0.195, + 0.20249999999999999, + 0.21, + 0.2175, + 0.22499999999999998, + 0.23249999999999998, + 0.24, + 0.2475, + 0.255, + 0.2625, + 0.27, + 0.27749999999999997, + 0.285, + 0.2925, + 0.3, + 0.3075, + 0.315, + 0.3225, + 0.32999999999999996, + 0.33749999999999997, + 0.345, + 0.3525, + 0.36, + 0.3675, + 0.375, + 0.3825, + 0.39, + 0.39749999999999996, + 0.40499999999999997, + 0.4125, + 0.42, + 0.4275, + 0.435, + 0.4425, + 0.44999999999999996, + 0.45749999999999996, + 0.46499999999999997, + 0.4725, + 0.48, + 0.4875, + 0.495, + 0.5025, + 0.51, + 0.5175, + 0.525, + 0.5325, + 0.54, + 0.5475, + 0.5549999999999999, + 0.5625, + 0.57, + 0.5775, + 0.585, + 0.5925, + 0.6, + 0.6074999999999999, + 0.615, + 0.6224999999999999, + 0.63, + 0.6375, + 0.645, + 0.6525, + 0.6599999999999999, + 0.6675, + 0.6749999999999999, + 0.6825, + 0.69, + 0.6975, + 0.705, + 0.7125, + 0.72, + 0.7274999999999999, + 0.735, + 0.7424999999999999, + 0.75, + 0.7575, + 0.765, + 0.7725, + 0.78, + 0.7875, + 0.7949999999999999, + 0.8025, + 0.8099999999999999, + 0.8175, + 0.825, + 0.8325, + 0.84, + 0.8474999999999999, + 0.855, + 0.8624999999999999, + 0.87, + 0.8775, + 0.885, + 0.8925, + 0.8999999999999999, + 0.9075, + 0.9149999999999999, + 0.9225, + 0.9299999999999999, + 0.9375, + 0.945, + 0.9525, + 0.96, + 0.9674999999999999, + 0.975, + 0.9824999999999999, + 0.99, + 0.9974999999999999, + 1.005, + 1.0125, + 1.02, + 1.0274999999999999, + 1.035, + 1.0425, + 1.05, + 1.0574999999999999, + 1.065, + 1.0725, + 1.08, + 1.0875, + 1.095, + 1.1025, + 1.1099999999999999, + 1.1175, + 1.125, + 1.1325, + 1.14, + 1.1475, + 1.155, + 1.1624999999999999, + 1.17, + 1.1775, + 1.185, + 1.1925, + 1.2, + 1.2075, + 1.2149999999999999, + 1.2225, + 1.23, + 1.2375, + 1.2449999999999999, + 1.2525, + 1.26, + 1.2674999999999998, + 1.275, + 1.2825, + 1.29, + 1.2974999999999999, + 1.305, + 1.3125, + 1.3199999999999998, + 1.3275, + 1.335, + 1.3425, + 1.3499999999999999, + 1.3575, + 1.365, + 1.3725, + 1.38, + 1.3875, + 1.395, + 1.4024999999999999, + 1.41, + 1.4175, + 1.425, + 1.4324999999999999, + 1.44, + 1.4475, + 1.4549999999999998, + 1.4625, + 1.47, + 1.4775, + 1.4849999999999999, + 1.4925, + 1.5, + 1.5074999999999998, + 1.515, + 1.5225, + 1.53, + 1.5374999999999999, + 1.545, + 1.5525, + 1.56, + 1.5675, + 1.575, + 1.5825, + 1.5899999999999999, + 1.5975, + 1.605, + 1.6125, + 1.6199999999999999, + 1.6275, + 1.635, + 1.6424999999999998, + 1.65, + 1.6575, + 1.665, + 1.6724999999999999, + 1.68, + 1.6875, + 1.6949999999999998, + 1.7025, + 1.71, + 1.7175, + 1.7249999999999999, + 1.7325, + 1.74, + 1.7474999999999998, + 1.755, + 1.7625, + 1.77, + 1.7774999999999999, + 1.785, + 1.7925, + 1.7999999999999998, + 1.8074999999999999, + 1.815, + 1.8225, + 1.8299999999999998, + 1.8375, + 1.845, + 1.8525, + 1.8599999999999999, + 1.8675, + 1.875, + 1.8824999999999998, + 1.89, + 1.8975, + 1.905, + 1.9124999999999999, + 1.92, + 1.9275, + 1.9349999999999998, + 1.9425, + 1.95, + 1.9575, + 1.9649999999999999, + 1.9725, + 1.98, + 1.9874999999999998, + 1.9949999999999999, + 2.0025, + 2.01, + 2.0175, + 2.025, + 2.0324999999999998, + 2.04, + 2.0475, + 2.0549999999999997, + 2.0625, + 2.07, + 2.0775, + 2.085, + 2.0925, + 2.1, + 2.1075, + 2.1149999999999998, + 2.1225, + 2.13, + 2.1374999999999997, + 2.145, + 2.1525, + 2.16, + 2.1675, + 2.175, + 2.1825, + 2.19, + 2.1975, + 2.205, + 2.2125, + 2.2199999999999998, + 2.2275, + 2.235, + 2.2424999999999997, + 2.25, + 2.2575, + 2.265, + 2.2725, + 2.28, + 2.2875, + 2.295, + 2.3024999999999998, + 2.31, + 2.3175, + 2.3249999999999997, + 2.3325, + 2.34, + 2.3474999999999997, + 2.355, + 2.3625, + 2.37, + 2.3775, + 2.385, + 2.3925, + 2.4, + 2.4074999999999998, + 2.415, + 2.4225, + 2.4299999999999997, + 2.4375, + 2.445, + 2.4525, + 2.46, + 2.4675, + 2.475, + 2.4825, + 2.4899999999999998, + 2.4975, + 2.505, + 2.5124999999999997, + 2.52, + 2.5275, + 2.5349999999999997, + 2.5425, + 2.55, + 2.5575, + 2.565, + 2.5725, + 2.58, + 2.5875, + 2.5949999999999998, + 2.6025, + 2.61, + 2.6174999999999997, + 2.625, + 2.6325, + 2.6399999999999997, + 2.6475, + 2.655, + 2.6625, + 2.67, + 2.6774999999999998, + 2.685, + 2.6925, + 2.6999999999999997, + 2.7075, + 2.715, + 2.7224999999999997, + 2.73, + 2.7375, + 2.745, + 2.7525, + 2.76, + 2.7675, + 2.775, + 2.7824999999999998, + 2.79, + 2.7975, + 2.8049999999999997, + 2.8125, + 2.82, + 2.8274999999999997, + 2.835, + 2.8425, + 2.85, + 2.8575, + 2.8649999999999998, + 2.8725, + 2.88, + 2.8874999999999997, + 2.895, + 2.9025, + 2.9099999999999997, + 2.9175, + 2.925, + 2.9325, + 2.94, + 2.9475, + 2.955, + 2.9625, + 2.9699999999999998, + 2.9775, + 2.985, + 2.9924999999999997, + 3.0 + ], + "method": "uniform_tempo_single_spin_runtime_validation", + "model": { + "counterterm": false, + "counterterm_strength": 0.0, + "drive_amplitude": 0.2, + "drive_frequency": 1.0, + "j": 0.0, + "n": 1, + "normalization": "bounded", + "omega": 1.0 + }, + "phase_state": { + "imag": [ + [ + 8.178808486123777e-05, + 0.3214524432432307 + ], + [ + -0.3215171909840441, + -8.178808486123777e-05 + ] + ], + "real": [ + [ + 0.6387540788942603, + -0.17669544164632084 + ], + [ + -0.1767535416258371, + 0.3612459211057398 + ] + ] + }, + "uniform_tempo_revision": "b76a018c32e5415989761d902b1b0e95f1a337da" +} \ No newline at end of file