commit 9f62be3736af1154ace84645831c6c2ea63b40cb Author: unanmed <1319491857@qq.com> Date: Sat Mar 15 22:26:31 2025 +0800 init: Minamo Model diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..284b6bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +result +node_modules \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..a6c7c28 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +*.js diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..6ff0062 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,13 @@ +{ + "printWidth": 80, + "tabWidth": 4, + "useTabs": false, + "semi": true, + "singleQuote": true, + "quoteProps": "as-needed", + "bracketSpacing": true, + "vueIndentScriptAndStyle": false, + "arrowParens": "avoid", + "trailingComma": "none", + "endOfLine": "auto" +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..91ab6ff --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python 调试程序: 模块", + "type": "debugpy", + "request": "launch", + "module": "ginka.topology.test" + } + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..22d2132 --- /dev/null +++ b/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) 2025 + + 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/README.md b/README.md new file mode 100644 index 0000000..3c1acb9 --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +# GINKA 地图生成器 + +GINKA Model 是一个用于生成网格状魔塔地图的模型,采用 UNet 网络,允许输入自然语言,指定地图大小。 + +GINKA Model 内部集成了 Minamo Model 用于判别两个地图的相似性,用于计算损失值,指导 GINKA Model 训练,避免了传统拓扑图相似度计算的不可微性质,提高模型训练性能。 + +## 贡献 GINKA Model 数据集 + +对于 HTML5 魔塔,如果你想要贡献数据集,需要对你的魔塔进行手动数据处理,流程如下: + +1. 选择楼层,可以是剧情层、战斗层等,但是需要满足下述条件 +2. 楼层除边缘外不应出现墙壁堆叠(例如 2\*2,边缘可以有重叠) +3. 楼层中不应该有闲置怪,不应该有连续 3 个以上的怪物,不应该有无法到达的区域,不宜有过多的入口 +4. 最外面一层围上一圈墙壁(箭头楼层切换除外) +5. 将所有的墙壁换成黄墙(数字 1) +6. 将所有的血瓶换成红血瓶(数字 31),所有红宝石换成最基础的红宝石(数字 27),蓝宝石换成最基础的蓝宝石(数字 28),删除除此之外的资源,剑盾可以当成红蓝宝石看待 +7. 所有钥匙换成黄钥匙(数字 21),所有门换成黄门(数字 81) +8. 所有箭头换成样板原版箭头(数字 161 至 164),所有上下楼梯换成样板原版楼梯(数字 87 和 88) +9. 怪物分为三个强度,弱怪,中怪,强怪,弱怪换为绿头怪(数字 201),中怪换成红头怪(数字 202),强怪换成青头怪(数字 203) +10. 在 `project` 文件夹下创建 `ginka-config.json` 文件,双击进入编辑,粘贴如下模板: + +```json +{ + "clip": { + "defaults": [0, 0, 13, 13], + "special": { + "MT11": [3, 3, 7, 7] + } + }, + "data": { + "MT1": ["MT1 楼层的第一个描述", "MT1 楼层的第二个描述"] + } +} +``` + +其中,`clip` 属性表示你的每张地图的那一部分会被当成数据集,例如填写 `[0, 0, 13, 13]` 就会让坐标为 `(0, 0)`,长宽为 `(13, 13)` 的矩形内容作为数据集。`special` 属性允许你针对单独的某几层设置不同的裁剪方式,例如设置 `MT11` 为 `[3, 3, 7, 7]` 等,如果没有设置默认使用 `defaults` 的裁剪方式。最好保证每个楼层大小一致,不然我还要手动分类。 + +`data` 是你的每一层的楼层描述,要求每一层都要有描述,每层描述可以有多个,要求可以准确或粗略描述楼层的一部分特征(不需要是全部,不然的话文字量会很大),不超过 64 个 token(每个中文字约 0.6 token,每个英文字母约 0.3 token),可以详细可以简略,推荐每层有三个描述以上,而且描述不要有过多的语义重复。 + +11. 在全塔属性中的楼层列表中去除不在数据集内的楼层 +12. 将 `project` 文件夹打包发给我即可 + +## 贡献 Minamo Model 数据集 + +首先需要对你的塔的地图进行处理,参考 [GINKA Model 数据处理方式](#贡献-ginka-model-数据集)的 1-9 和 11 步,同时最好每张地图至少有几个空格(没有也没有影响,只不过会导致这个地图参与训练的次数降低) + +与 GINKA Model 类似,在 `project` 文件夹中添加 `minamo-config.json` 文件,打开后将如下模板粘贴进去: + +```json +{ + "clip": { + "defaults": [0, 0, 13, 13], + "special": { + "MT11": [3, 3, 7, 7] + } + } +} +``` + +其中 `clip` 属性与 GINKA Model 的定义相同,参考上一小节即可。 + +将以上内容全部设置完毕后,将 `project` 文件夹打包发送给我,添加 `ginka-config.json` 时也可以作为 GINKA Model 训练集。 + +## 训练 + +如果你想自行训练模型,首先需要安装 `reqiurements.txt` 中所需的库,然后按照以下顺序操作: + +1. 准备 Minamo Model 数据集,放置在根目录下,命名为 `minamo-dataset.json` +2. 执行 `python -m minamo.train`,等待训练完毕 +3. 准备 GINKA Model 数据集,放置在根目录下,命名为 `ginka-dataset.json` +4. 执行 `python -m ginka.train`,等待训练完毕 +5. 目录 `result/ginka.pth` 即为训练完毕的 GINKA 模型 +6. (可选)准备 Minamo Model / GINKA Model 验证集,放置在根目录下,命名为 `minamo-eval.json` / `ginka-eval.json`,训练时每 10 个 epoch 会进行一次验证推理,建议使用与训练集不同的塔数据作为验证集。 + +准备训练集和验证集时,可以使用命令行脚本自动处理。首先进入 `data` 文件夹,然后运行 `pnpm i` 安装所有依赖,然后执行下面的脚本: + +```bash +pnpm ginka "../ginka-dataset.json" "MyTower/project" # 通过 ginka-config.json 生成 GINKA 训练集 +pnpm ginka "../ginka-eval.json" "MyTower/project" # 通过 ginka-config.json 生成 GINKA 验证集 +pnpm minamo "../minamo-dataset.json" "MyTower/project" # 通过 minamo-config.json 生成 Minamo 训练集 +pnpm minamo "../minamo-eval.json" "MyTower/project" # 通过 minamo-config.json 生成 Minamo 验证集 +``` diff --git a/data/package.json b/data/package.json new file mode 100644 index 0000000..d8f90fc --- /dev/null +++ b/data/package.json @@ -0,0 +1,25 @@ +{ + "name": "data", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "ginka": "tsx ./src/ginka.ts", + "minamo": "tsx ./src/minamo.ts", + "test:topo": "tsx ./src/topology/test.ts", + "test:vision": "tsx ./src/vision/test.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "packageManager": "pnpm@10.5.2", + "devDependencies": { + "@types/fs-extra": "^11.0.4", + "@types/node": "^22.13.10", + "tsx": "^4.19.3", + "vitest": "^3.0.8" + }, + "dependencies": { + "fs-extra": "^11.3.0" + } +} diff --git a/data/pnpm-lock.yaml b/data/pnpm-lock.yaml new file mode 100644 index 0000000..d0a1d18 --- /dev/null +++ b/data/pnpm-lock.yaml @@ -0,0 +1,966 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + fs-extra: + specifier: ^11.3.0 + version: 11.3.0 + devDependencies: + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 + '@types/node': + specifier: ^22.13.10 + version: 22.13.10 + tsx: + specifier: ^4.19.3 + version: 4.19.3 + vitest: + specifier: ^3.0.8 + version: 3.0.8(@types/node@22.13.10)(tsx@4.19.3) + +packages: + + '@esbuild/aix-ppc64@0.25.1': + resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.1': + resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.1': + resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.1': + resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.1': + resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.1': + resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.1': + resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.1': + resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.1': + resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.1': + resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.1': + resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.1': + resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.1': + resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.1': + resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.1': + resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.1': + resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.1': + resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.1': + resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.1': + resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.1': + resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.1': + resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.1': + resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.1': + resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.1': + resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.1': + resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@rollup/rollup-android-arm-eabi@4.35.0': + resolution: {integrity: sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.35.0': + resolution: {integrity: sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.35.0': + resolution: {integrity: sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.35.0': + resolution: {integrity: sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.35.0': + resolution: {integrity: sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.35.0': + resolution: {integrity: sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.35.0': + resolution: {integrity: sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.35.0': + resolution: {integrity: sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.35.0': + resolution: {integrity: sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.35.0': + resolution: {integrity: sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loongarch64-gnu@4.35.0': + resolution: {integrity: sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-powerpc64le-gnu@4.35.0': + resolution: {integrity: sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.35.0': + resolution: {integrity: sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-s390x-gnu@4.35.0': + resolution: {integrity: sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.35.0': + resolution: {integrity: sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.35.0': + resolution: {integrity: sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-win32-arm64-msvc@4.35.0': + resolution: {integrity: sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.35.0': + resolution: {integrity: sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.35.0': + resolution: {integrity: sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/fs-extra@11.0.4': + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + + '@types/jsonfile@6.1.4': + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + + '@types/node@22.13.10': + resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==} + + '@vitest/expect@3.0.8': + resolution: {integrity: sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==} + + '@vitest/mocker@3.0.8': + resolution: {integrity: sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.0.8': + resolution: {integrity: sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==} + + '@vitest/runner@3.0.8': + resolution: {integrity: sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==} + + '@vitest/snapshot@3.0.8': + resolution: {integrity: sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==} + + '@vitest/spy@3.0.8': + resolution: {integrity: sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==} + + '@vitest/utils@3.0.8': + resolution: {integrity: sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + + esbuild@0.25.1: + resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.2.0: + resolution: {integrity: sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==} + engines: {node: '>=12.0.0'} + + fs-extra@11.3.0: + resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} + engines: {node: '>=14.14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-tsconfig@4.10.0: + resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.9: + resolution: {integrity: sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + rollup@4.35.0: + resolution: {integrity: sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.8.1: + resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + tsx@4.19.3: + resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==} + engines: {node: '>=18.0.0'} + hasBin: true + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + vite-node@3.0.8: + resolution: {integrity: sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@6.2.2: + resolution: {integrity: sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.0.8: + resolution: {integrity: sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.0.8 + '@vitest/ui': 3.0.8 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + +snapshots: + + '@esbuild/aix-ppc64@0.25.1': + optional: true + + '@esbuild/android-arm64@0.25.1': + optional: true + + '@esbuild/android-arm@0.25.1': + optional: true + + '@esbuild/android-x64@0.25.1': + optional: true + + '@esbuild/darwin-arm64@0.25.1': + optional: true + + '@esbuild/darwin-x64@0.25.1': + optional: true + + '@esbuild/freebsd-arm64@0.25.1': + optional: true + + '@esbuild/freebsd-x64@0.25.1': + optional: true + + '@esbuild/linux-arm64@0.25.1': + optional: true + + '@esbuild/linux-arm@0.25.1': + optional: true + + '@esbuild/linux-ia32@0.25.1': + optional: true + + '@esbuild/linux-loong64@0.25.1': + optional: true + + '@esbuild/linux-mips64el@0.25.1': + optional: true + + '@esbuild/linux-ppc64@0.25.1': + optional: true + + '@esbuild/linux-riscv64@0.25.1': + optional: true + + '@esbuild/linux-s390x@0.25.1': + optional: true + + '@esbuild/linux-x64@0.25.1': + optional: true + + '@esbuild/netbsd-arm64@0.25.1': + optional: true + + '@esbuild/netbsd-x64@0.25.1': + optional: true + + '@esbuild/openbsd-arm64@0.25.1': + optional: true + + '@esbuild/openbsd-x64@0.25.1': + optional: true + + '@esbuild/sunos-x64@0.25.1': + optional: true + + '@esbuild/win32-arm64@0.25.1': + optional: true + + '@esbuild/win32-ia32@0.25.1': + optional: true + + '@esbuild/win32-x64@0.25.1': + optional: true + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@rollup/rollup-android-arm-eabi@4.35.0': + optional: true + + '@rollup/rollup-android-arm64@4.35.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.35.0': + optional: true + + '@rollup/rollup-darwin-x64@4.35.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.35.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.35.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.35.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.35.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.35.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.35.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.35.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.35.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.35.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.35.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.35.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.35.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.35.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.35.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.35.0': + optional: true + + '@types/estree@1.0.6': {} + + '@types/fs-extra@11.0.4': + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 22.13.10 + + '@types/jsonfile@6.1.4': + dependencies: + '@types/node': 22.13.10 + + '@types/node@22.13.10': + dependencies: + undici-types: 6.20.0 + + '@vitest/expect@3.0.8': + dependencies: + '@vitest/spy': 3.0.8 + '@vitest/utils': 3.0.8 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.0.8(vite@6.2.2(@types/node@22.13.10)(tsx@4.19.3))': + dependencies: + '@vitest/spy': 3.0.8 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.2.2(@types/node@22.13.10)(tsx@4.19.3) + + '@vitest/pretty-format@3.0.8': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.0.8': + dependencies: + '@vitest/utils': 3.0.8 + pathe: 2.0.3 + + '@vitest/snapshot@3.0.8': + dependencies: + '@vitest/pretty-format': 3.0.8 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.0.8': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@3.0.8': + dependencies: + '@vitest/pretty-format': 3.0.8 + loupe: 3.1.3 + tinyrainbow: 2.0.0 + + assertion-error@2.0.1: {} + + cac@6.7.14: {} + + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 + + check-error@2.1.1: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + es-module-lexer@1.6.0: {} + + esbuild@0.25.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.1 + '@esbuild/android-arm': 0.25.1 + '@esbuild/android-arm64': 0.25.1 + '@esbuild/android-x64': 0.25.1 + '@esbuild/darwin-arm64': 0.25.1 + '@esbuild/darwin-x64': 0.25.1 + '@esbuild/freebsd-arm64': 0.25.1 + '@esbuild/freebsd-x64': 0.25.1 + '@esbuild/linux-arm': 0.25.1 + '@esbuild/linux-arm64': 0.25.1 + '@esbuild/linux-ia32': 0.25.1 + '@esbuild/linux-loong64': 0.25.1 + '@esbuild/linux-mips64el': 0.25.1 + '@esbuild/linux-ppc64': 0.25.1 + '@esbuild/linux-riscv64': 0.25.1 + '@esbuild/linux-s390x': 0.25.1 + '@esbuild/linux-x64': 0.25.1 + '@esbuild/netbsd-arm64': 0.25.1 + '@esbuild/netbsd-x64': 0.25.1 + '@esbuild/openbsd-arm64': 0.25.1 + '@esbuild/openbsd-x64': 0.25.1 + '@esbuild/sunos-x64': 0.25.1 + '@esbuild/win32-arm64': 0.25.1 + '@esbuild/win32-ia32': 0.25.1 + '@esbuild/win32-x64': 0.25.1 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + + expect-type@1.2.0: {} + + fs-extra@11.3.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fsevents@2.3.3: + optional: true + + get-tsconfig@4.10.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + graceful-fs@4.2.11: {} + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + loupe@3.1.3: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + ms@2.1.3: {} + + nanoid@3.3.9: {} + + pathe@2.0.3: {} + + pathval@2.0.0: {} + + picocolors@1.1.1: {} + + postcss@8.5.3: + dependencies: + nanoid: 3.3.9 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + resolve-pkg-maps@1.0.0: {} + + rollup@4.35.0: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.35.0 + '@rollup/rollup-android-arm64': 4.35.0 + '@rollup/rollup-darwin-arm64': 4.35.0 + '@rollup/rollup-darwin-x64': 4.35.0 + '@rollup/rollup-freebsd-arm64': 4.35.0 + '@rollup/rollup-freebsd-x64': 4.35.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.35.0 + '@rollup/rollup-linux-arm-musleabihf': 4.35.0 + '@rollup/rollup-linux-arm64-gnu': 4.35.0 + '@rollup/rollup-linux-arm64-musl': 4.35.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.35.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.35.0 + '@rollup/rollup-linux-riscv64-gnu': 4.35.0 + '@rollup/rollup-linux-s390x-gnu': 4.35.0 + '@rollup/rollup-linux-x64-gnu': 4.35.0 + '@rollup/rollup-linux-x64-musl': 4.35.0 + '@rollup/rollup-win32-arm64-msvc': 4.35.0 + '@rollup/rollup-win32-ia32-msvc': 4.35.0 + '@rollup/rollup-win32-x64-msvc': 4.35.0 + fsevents: 2.3.3 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.8.1: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinypool@1.0.2: {} + + tinyrainbow@2.0.0: {} + + tinyspy@3.0.2: {} + + tsx@4.19.3: + dependencies: + esbuild: 0.25.1 + get-tsconfig: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + + undici-types@6.20.0: {} + + universalify@2.0.1: {} + + vite-node@3.0.8(@types/node@22.13.10)(tsx@4.19.3): + dependencies: + cac: 6.7.14 + debug: 4.4.0 + es-module-lexer: 1.6.0 + pathe: 2.0.3 + vite: 6.2.2(@types/node@22.13.10)(tsx@4.19.3) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@6.2.2(@types/node@22.13.10)(tsx@4.19.3): + dependencies: + esbuild: 0.25.1 + postcss: 8.5.3 + rollup: 4.35.0 + optionalDependencies: + '@types/node': 22.13.10 + fsevents: 2.3.3 + tsx: 4.19.3 + + vitest@3.0.8(@types/node@22.13.10)(tsx@4.19.3): + dependencies: + '@vitest/expect': 3.0.8 + '@vitest/mocker': 3.0.8(vite@6.2.2(@types/node@22.13.10)(tsx@4.19.3)) + '@vitest/pretty-format': 3.0.8 + '@vitest/runner': 3.0.8 + '@vitest/snapshot': 3.0.8 + '@vitest/spy': 3.0.8 + '@vitest/utils': 3.0.8 + chai: 5.2.0 + debug: 4.4.0 + expect-type: 1.2.0 + magic-string: 0.30.17 + pathe: 2.0.3 + std-env: 3.8.1 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 2.0.0 + vite: 6.2.2(@types/node@22.13.10)(tsx@4.19.3) + vite-node: 3.0.8(@types/node@22.13.10)(tsx@4.19.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.13.10 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 diff --git a/data/src/floor.ts b/data/src/floor.ts new file mode 100644 index 0000000..3de9db4 --- /dev/null +++ b/data/src/floor.ts @@ -0,0 +1,43 @@ +const numMap: Record = { + 0: 0, // 空地 + 1: 1, // 墙壁 + 21: 2, // 钥匙 + 27: 3, // 红宝石 + 28: 4, // 蓝宝石 + 31: 5, // 血瓶 + 81: 6, // 门 + 201: 7, // 弱怪 + 202: 8, // 中怪 + 203: 9, // 强怪 + 87: 10, // 楼梯 + 88: 10, // 楼梯 + 161: 11, // 箭头 + 162: 11, // 箭头 + 163: 11, // 箭头 + 164: 11 // 箭头 +}; + +export function convertFloor( + map: number[][], + [x, y, w, h]: [number, number, number, number], + name: string, + floorId: string +) { + const clipped: number[][] = []; + + for (let nx = x; nx < x + w; nx++) { + const row: number[] = []; + for (let ny = y; ny < y + h; ny++) { + const num = numMap[map[nx][ny]]; + if (num === void 0) { + console.log( + `⚠️ 魔塔 ${name} 的楼层 ${floorId} 中出现未知图块类型:${map[nx][ny]}` + ); + } + row.push(num ?? 0); + } + clipped.push(row); + } + + return clipped; +} diff --git a/data/src/ginka.ts b/data/src/ginka.ts new file mode 100644 index 0000000..b150304 --- /dev/null +++ b/data/src/ginka.ts @@ -0,0 +1,82 @@ +import { exists, writeFile } from 'fs-extra'; +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { convertFloor } from './floor'; +import { mergeDataset } from './utils'; + +interface GinkaConfig { + clip: { + defaults: [number, number, number, number]; + special: Record; + }; + data: Record; +} + +interface GinkaTrainData { + text: string[]; + map: number[][]; + size: [number, number]; +} + +interface GinkaDataset { + datasetId: number; + data: Record; +} + +const [output, ...list] = process.argv.slice(2); + +async function parseOneFloor( + path: string, + name: string, + floorId: string, + config: GinkaConfig +): Promise { + const floorFile = await readFile(path, 'utf-8'); + const floor: any = JSON.parse(floorFile.split('\n').slice(1).join('\n')); + const map = floor.map as number[][]; + const clip = config.clip.special[floorId] ?? config.clip.defaults; + + const clipped = convertFloor(map, clip, name, floorId); + + if (!config.data[floorId]) { + console.log(`⚠️ 魔塔 ${name} 的楼层 ${floorId} 不存在描述文本!`); + } + + const data: GinkaTrainData = { + text: config.data[floorId], + map: clipped, + size: [clipped[0].length, clipped.length] + }; + + return data; +} + +async function parseOne(path: string): Promise { + const dataFile = await readFile(join(path, 'data.js'), 'utf-8'); + const configFile = await readFile(join(path, 'ginka-config.json'), 'utf-8'); + const data: any = JSON.parse(dataFile.split('\n').slice(1).join('\n')); + const config = JSON.parse(configFile) as GinkaConfig; + const floorIds = data.main.floorIds as string[]; + const name = data.firstData.name as string; + + const datas = await Promise.all( + floorIds.map(v => + parseOneFloor(join(path, 'floors', `${v}.js`), name, v, config) + ) + ); + + const dataset: GinkaDataset = { + datasetId: Math.floor(Math.random() * 1e12), + data: Object.fromEntries(datas.map((v, i) => [floorIds[i], v])) + }; + + return dataset; +} + +(async () => { + const results = await Promise.all(list.map(v => parseOne(v))); + const dataset = mergeDataset(...results); + await writeFile(output, JSON.stringify(dataset, void 0), 'utf-8'); + const size = Object.keys(dataset.data).length; + console.log(`✅ 已处理 ${list.length} 个塔,共 ${size} 个地图`); +})(); diff --git a/data/src/minamo.ts b/data/src/minamo.ts new file mode 100644 index 0000000..cca6f54 --- /dev/null +++ b/data/src/minamo.ts @@ -0,0 +1,337 @@ +import { readFile, writeFile } from 'fs-extra'; +import { join } from 'path'; +import { convertFloor } from './floor'; +import { mergeDataset } from './utils'; +import { compareMap } from './topology/compare'; +import { mirrorMapX, mirrorMapY, rotateMap } from './topology/transform'; +import { directions, tileType } from './topology/graph'; +import { calculateVisualSimilarity } from './vision/similarity'; + +interface MinamoConfig { + clip: { + defaults: [number, number, number, number]; + special: Record; + }; + // data: Record>; +} + +interface MinamoTrainData { + map1: number[][]; + map2: number[][]; + topoSimilarity: number; + visionSimilarity: number; + size: [number, number]; +} + +interface MinamoDataset { + datasetId: number; + data: Record; +} + +const [output, ...list] = process.argv.slice(2); + +function chooseFrom(arr: T[], n: number): T[] { + const copy = arr.slice(); + for (let i = copy.length - 1; i > 0; i--) { + let randIndex = Math.floor(Math.random() * (i + 1)); + [copy[i], copy[randIndex]] = [copy[randIndex], copy[i]]; + } + return copy.slice(0, n); +} + +function choosePair(n: number) { + const totalCount = Math.round((n * (n - 1)) / 2); + const count = Math.min(totalCount, 1000); + const pairs: number[] = []; + for (let i = 0; i < n; i++) { + for (let j = i + 1; j < n; j++) { + pairs.push(i * n + j); + } + } + // 直接打乱后取前 count 个 + for (let i = pairs.length - 1; i > 0; i--) { + let randIndex = Math.floor(Math.random() * (i + 1)); + [pairs[i], pairs[randIndex]] = [pairs[randIndex], pairs[i]]; + } + + return pairs.slice(0, count); +} + +function transform(map: number[][], rot: number, flip: number) { + let res = map; + for (let i = 0; i < rot; i++) { + res = rotateMap(res); + } + if (flip & 0b01) { + res = mirrorMapX(res); + } + if (flip & 0b10) { + res = mirrorMapY(res); + } + return res; +} + +function generateTransformData( + id1: string, + id2: string, + map1: number[][], + map2: number[][], + simi: number +) { + const types: [rot: number, flip: number][] = []; + for (const rot of [0, 1, 2, 3]) { + for (const flip of [0b00, 0b01, 0b10, 0b11]) { + if (rot === 0 && flip === 0) continue; + types.push([rot, flip]); + } + } + // 随机抽取最多两个 + const trans = chooseFrom(types, Math.floor(Math.random() * 3)); + return trans + .map(([rot, flip]) => { + const com1 = `${id1}.${rot}.${flip}:${id1}`; + const com2 = `${id1}.${rot}.${flip}:${id2}`; + const com3 = `${id2}.${rot}.${flip}:${id1}`; + const com4 = `${id2}.${rot}.${flip}:${id2}`; + const choose = chooseFrom( + [com1, com2, com3, com4], + Math.floor(Math.random() * 2) + ); + const res: [id: string, data: MinamoTrainData][] = []; + if (choose.includes(com1)) { + const t = transform(map1, rot, flip); + res.push([ + com1, + { + map1: t, + map2: map1, + topoSimilarity: 1, + visionSimilarity: calculateVisualSimilarity(map1, t), + size: [map1[0].length, map1.length] + } + ]); + } + if (choose.includes(com2)) { + const t = transform(map1, rot, flip); + res.push([ + com2, + { + map1: t, + map2: map2, + topoSimilarity: simi, + visionSimilarity: calculateVisualSimilarity(t, map2), + size: [map1[0].length, map1.length] + } + ]); + } + if (choose.includes(com3)) { + const t = transform(map2, rot, flip); + res.push([ + com3, + { + map1: t, + map2: map1, + topoSimilarity: simi, + visionSimilarity: calculateVisualSimilarity(t, map1), + size: [map1[0].length, map1.length] + } + ]); + } + if (choose.includes(com4)) { + const t = transform(map2, rot, flip); + res.push([ + com4, + { + map1: t, + map2: map2, + topoSimilarity: 1, + visionSimilarity: calculateVisualSimilarity(t, map2), + size: [map1[0].length, map1.length] + } + ]); + } + + return res; + }) + .flat(); +} + +function generateSimilarData(id: string, map: number[][]) { + // 生成最多五个微调地图 + const width = map[0].length; + const height = map.length; + const num = Math.floor(Math.random() * 6); + const res: [id: string, data: MinamoTrainData][] = []; + + for (let i = 0; i < num; i++) { + const clone = map.map(v => v.slice()); + const prob = Math.random() * 0.3; + for (let ny = 0; ny < height; ny++) { + for (let nx = 0; nx < width; nx++) { + if (Math.random() > prob) { + // 有一定的概率进行微调 + continue; + } + if (Math.random() < 0.2) { + // 20% 概率与旁边图块互换位置 + const [dx, dy] = + directions[ + Math.floor(Math.random() * directions.length) + ]; + const px = nx + dx; + const py = ny + dy; + if (px < 0 || px >= width || py < 0 || py >= height) { + continue; + } + [clone[ny][nx], clone[py][px]] = [ + clone[py][px], + clone[ny][nx] + ]; + } else { + // 80% 概率替换当前图块 + clone[ny][nx] = Math.floor(Math.random() * tileType.size); + } + } + } + const id2 = `${id}.S${i}`; + const sid = `${id}:${id2}`; + const simi = compareMap(id, id2, map, clone); + res.push([ + sid, + { + map1: map, + map2: clone, + size: [width, height], + topoSimilarity: simi, + visionSimilarity: calculateVisualSimilarity(map, clone) + } + ]); + } + return res; +} + +function generateDataset( + floors: Map, + pairs: number[], + floorIds: string[], + config: MinamoConfig +): Record { + const data: Record = {}; + + pairs.forEach(v => { + const num1 = Math.floor(v / floorIds.length); + const num2 = v % floorIds.length; + const id1 = floorIds[num1]; + const id2 = floorIds[num2]; + const map1 = floors.get(id1); + const map2 = floors.get(id2); + if (!map1 || !map2) return; + const [w1, h1] = [map1[0].length, map1.length]; + const [w2, h2] = [map2[0].length, map2.length]; + if (w1 !== w2 || h1 !== h2) return; + const topoSimilarity = compareMap(id1, id2, map1, map2); + const visionSimilarity = calculateVisualSimilarity(map1, map2); + const train: MinamoTrainData = { + map1, + map2, + topoSimilarity, + visionSimilarity, + size: [w1, h1] + }; + data[`${id1}:${id2}`] = train; + // 自身与自身对比的训练集,保证模型对相同地图输出 1 + const self1 = `${id1}:${id1}`; + const self2 = `${id2}:${id2}`; + const selfTrain = chooseFrom( + [self1, self2], + Math.floor(Math.random() * 3) + ); + if (selfTrain.includes(self1) && !data[`${id1}:${id1}`]) { + const selfTrain1: MinamoTrainData = { + map1: map1, + map2: map1, + topoSimilarity: 1, + visionSimilarity: 1, + size: [w1, h1] + }; + data[`${id1}:${id1}`] = selfTrain1; + } + if (selfTrain.includes(self2) && !data[`${id2}:${id2}`]) { + const selfTrain2: MinamoTrainData = { + map1: map2, + map2: map2, + topoSimilarity: 1, + visionSimilarity: 1, + size: [w1, h1] + }; + data[`${id2}:${id2}`] = selfTrain2; + } + // 翻转、旋转训练集 + Object.assign( + data, + Object.fromEntries( + generateTransformData(id1, id2, map1, map2, topoSimilarity) + ) + ); + // 地图微调训练集 + Object.assign(data, Object.fromEntries(generateSimilarData(id1, map1))); + // Object.assign(data, Object.fromEntries(generateSimilarData(id2, map2))); + }); + + return data; +} + +async function parseOne(path: string): Promise { + const dataFile = await readFile(join(path, 'data.js'), 'utf-8'); + const configFile = await readFile( + join(path, 'minamo-config.json'), + 'utf-8' + ); + const data: any = JSON.parse(dataFile.split('\n').slice(1).join('\n')); + const config = JSON.parse(configFile) as MinamoConfig; + const floorIds = data.main.floorIds as string[]; + const name = data.firstData.name as string; + const length = floorIds.length; + const totalCount = Math.round((length * (length - 1)) / 2); + + const pairs = choosePair(length); + + console.log( + `✅ 在 ${name} 中发现 ${length} 个楼层,共 ${totalCount} 种组合,选取 ${pairs.length} 个组合` + ); + + const floors = new Map( + await Promise.all( + floorIds.map>(async v => { + const file = await readFile( + join(path, 'floors', `${v}.js`), + 'utf-8' + ); + const data = file.split('\n').slice(1).join('\n'); + const json = JSON.parse(data); + const map = json.map; + const clip = config.clip.special[v] ?? config.clip.defaults; + // 裁剪 + const clipped = convertFloor(map, clip, name, v); + return [v, clipped]; + }) + ) + ); + + const trainData = generateDataset(floors, pairs, floorIds, config); + + const dataset: MinamoDataset = { + datasetId: Math.floor(Math.random() * 1e12), + data: trainData + }; + + return dataset; +} + +(async () => { + const results = await Promise.all(list.map(v => parseOne(v))); + const dataset = mergeDataset(...results); + await writeFile(output, JSON.stringify(dataset, void 0), 'utf-8'); + const size = Object.keys(dataset.data).length; + console.log(`✅ 已处理 ${list.length} 个塔,共 ${size} 个组合`); +})(); diff --git a/data/src/topology/compare.ts b/data/src/topology/compare.ts new file mode 100644 index 0000000..0a08eaf --- /dev/null +++ b/data/src/topology/compare.ts @@ -0,0 +1,29 @@ +import { buildTopologicalGraph } from './graph'; +import { GinkaTopologicalGraphs } from './interface'; +import { overallSimilarity } from './similarity'; + +const cache = new Map(); + +export function getTopologicalGraph( + floorId: string, + map: number[][] +): GinkaTopologicalGraphs { + if (cache.has(floorId)) return cache.get(floorId)!; + const graphs = buildTopologicalGraph(map); + cache.set(floorId, graphs); + return graphs; +} + +export function compareMap( + floorId1: string, + floorId2: string, + map1: number[][], + map2: number[][] +) { + const graph1 = getTopologicalGraph(floorId1, map1); + const graph2 = getTopologicalGraph(floorId2, map2); + + const kernel = overallSimilarity(graph1, graph2); + + return kernel; +} diff --git a/data/src/topology/graph.ts b/data/src/topology/graph.ts new file mode 100644 index 0000000..eeee6a5 --- /dev/null +++ b/data/src/topology/graph.ts @@ -0,0 +1,254 @@ +import { + ResourceArea, + GinkaGraph, + BranchNode, + GinkaTopologicalGraphs, + ResourceNode +} from './interface'; + +export const tileType = new Set( + Array(12) + .fill(0) + .map((_, i) => i) +); +const branchType = new Set([6, 7, 8, 9]); +const entranceType = new Set([10, 11]); +const resourceType = new Set([0, 2, 3, 4, 5, 10, 11]); + +export const directions: [number, number][] = [ + [-1, 0], + [1, 0], + [0, -1], + [0, 1] +]; + +function buildGraphFromEntrance( + map: number[][], + entrance: number, + resourceMap: Map, + areaMap: ResourceArea[] +): GinkaGraph { + const width = map[0].length; + const height = map[1].length; + + const visitedEntrance = new Set([entrance]); + const visited = new Set(); + const queue: [number, number][] = []; + queue.push([entrance % width, Math.floor(entrance / width)]); + + const branchNodes = new Set(); + + // 1. BFS 检测所有分支节点 + while (queue.length > 0) { + const item = queue.shift(); + if (!item) continue; + const [nx, ny] = item; + const index = ny * width + nx; + if (visited.has(index)) continue; + const tile = map[ny][nx]; + + if (entranceType.has(tile)) { + visitedEntrance.add(index); + } + if (branchType.has(tile)) { + branchNodes.add(index); + } + visited.add(index); + + for (const [dx, dy] of directions) { + const px = dx + nx; + const py = dy + ny; + if (px < 0 || px >= width || py < 0 || py >= height) { + continue; + } + const tile = map[py][px]; + if (tile !== 1) { + // 非墙区域可通行 + queue.push([px, py]); + } + } + } + + // 2. 从分支节点构建拓扑图 + const graph = new Map(); + branchNodes.forEach(v => { + const nx = v % width; + const ny = Math.floor(v / width); + if (!graph.get(v)) { + graph.set(v, { + type: 'branch', + neighbor: new Set(), + tile: map[ny][nx] + }); + } + const node = graph.get(v)!; + for (const [dx, dy] of directions) { + const px = nx + dx; + const py = ny + dy; + if (px < 0 || px >= width || py < 0 || py >= height) { + continue; + } + const index = py * width + px; + + // 先检查临近节点是不是分支节点,是的话链接到自己 + if (branchNodes.has(index)) { + node.neighbor.add(index); + } else { + // 检查是不是资源节点 + const pointer = resourceMap.get(index); + if (pointer === void 0) continue; + const area = areaMap[pointer]; + if (!area) continue; + area.neighbor.add(v); + area.members.forEach(v => { + node.neighbor.add(v); + }); + } + } + }); + + // 3. 把资源节点拆分成并排,并放入拓扑图 + areaMap.forEach(v => { + v.members.forEach(index => { + const nx = index % width; + const ny = Math.floor(index / width); + const tile = map[ny][nx]; + if (tile === 0) return; + const node: ResourceNode = { + type: 'resource', + resourceType: tile, + neighbor: v.neighbor, + resourceArea: v + }; + graph.set(index, node); + }); + }); + + return { graph, resourceMap, areaMap, visitedEntrance, visited }; +} + +function findResourceNodes(map: number[][]) { + const width = map[0].length; + const height = map[1].length; + + const visited = new Set(); + const areas: ResourceArea[] = []; + const resourcesMap: Map = new Map(); + + for (let ny = 0; ny < height; ny++) { + for (let nx = 0; nx < width; nx++) { + const tile = map[ny][nx]; + const index = ny * width + nx; + if (visited.has(index) || !resourceType.has(tile)) { + continue; + } + const queue: [number, number][] = []; + queue.push([nx, ny]); + const area: ResourceArea = { + type: 'resource', + resources: new Map([[tile, 1]]), + members: new Set([index]), + neighbor: new Set() + }; + + while (queue.length > 0) { + const item = queue.shift(); + if (!item) continue; + const [nx, ny] = item; + const index = ny * width + nx; + if (visited.has(index)) { + continue; + } + const tile = map[ny][nx]; + if (!resourceType.has(tile)) { + continue; + } + visited.add(index); + + const exists = area.resources.get(tile); + if (!exists) { + area.resources.set(tile, 1); + } else { + area.resources.set(tile, exists + 1); + } + area.members.add(index); + resourcesMap.set(index, areas.length); + + for (const [dx, dy] of directions) { + const px = nx + dx; + const py = ny + dy; + if (px < 0 || px >= width || py < 0 || py >= height) { + continue; + } + queue.push([px, py]); + } + } + + areas.push(area); + } + } + + return { areaMap: areas, resourcesMap }; +} + +export function buildTopologicalGraph(map: number[][]): GinkaTopologicalGraphs { + const width = map[0].length; + const height = map[1].length; + + // 1. 找到所有入口 + const entrances = new Set(); + for (let ny = 0; ny < height; ny++) { + for (let nx = 0; nx < width; nx++) { + const tile = map[ny][nx]; + if (entranceType.has(tile)) { + entrances.add(ny * width + nx); + } + } + } + + // 2. 找到所有的资源节点 + const { areaMap, resourcesMap } = findResourceNodes(map); + + // 3. 对每个入口计算拓扑图 + const graphs: GinkaGraph[] = []; + const usedEntrance = new Set(); + const totalVisited = new Set(); + /** 入口位置到拓扑图的映射 */ + const entranceMap = new Map(); + entrances.forEach(v => { + if (usedEntrance.has(v)) { + return; + } + const nx = v % width; + const ny = Math.floor(v / width); + const entranceGraph = buildGraphFromEntrance( + map, + v, + resourcesMap, + areaMap + ); + const { graph, visited, visitedEntrance } = entranceGraph; + graphs.push(entranceGraph); + // 标记已经探索到的入口,并标记这个入口对应了哪个图 + visitedEntrance.forEach(v => { + usedEntrance.add(v); + entranceMap.set(v, entranceGraph); + }); + visited.forEach(v => { + totalVisited.add(v); + }); + }); + + // 3. 计算不可到达区域 + const unreachable = new Set(); + for (let ny = 0; ny < height; ny++) { + for (let nx = 0; nx < width; nx++) { + const index = ny * width + nx; + if (!totalVisited.has(index) && map[ny][nx] !== 1) { + unreachable.add(index); + } + } + } + + return { graphs, entranceMap, unreachable }; +} diff --git a/data/src/topology/interface.ts b/data/src/topology/interface.ts new file mode 100644 index 0000000..c038946 --- /dev/null +++ b/data/src/topology/interface.ts @@ -0,0 +1,43 @@ +export interface ResourceArea { + type: 'resource'; + resources: Map; + members: Set; + neighbor: Set; +} + +export interface BranchNode { + type: 'branch'; + neighbor: Set; + tile: number; +} + +export interface ResourceNode { + type: 'resource'; + resourceType: number; + neighbor: Set; + resourceArea: ResourceArea; +} + +export type GinkaNode = BranchNode | ResourceNode; + +export interface GinkaGraph { + /** 拓扑图内容,键表示位置,值表示这一点的节点 */ + graph: Map; + /** 资源指针,键表示位置,值表示这一点对应的资源节点在 areaMap 的索引 */ + resourceMap: Map; + /** 资源区域列表 */ + areaMap: ResourceArea[]; + /** 这个拓扑图包含的入口位置 */ + visitedEntrance: Set; + /** 这个拓扑图能够造访的所有位置 */ + visited: Set; +} + +export interface GinkaTopologicalGraphs { + /** 这个地图包含的所有独立的图 */ + graphs: GinkaGraph[]; + /** 每个入口对应哪个图 */ + entranceMap: Map; + /** 这个图从入口开始的不可到达区域 */ + unreachable: Set; +} diff --git a/data/src/topology/similarity.ts b/data/src/topology/similarity.ts new file mode 100644 index 0000000..94c2c84 --- /dev/null +++ b/data/src/topology/similarity.ts @@ -0,0 +1,171 @@ +import { cosineSimilarity } from 'src/utils'; +import { GinkaGraph, GinkaTopologicalGraphs } from './interface'; + +interface WLNode { + originalPos: number; + originalLabel: string; + currentLabel: string; + neighbors: WLNode[]; +} + +function encodeNodeLabels(graph: GinkaGraph) { + const nodes: WLNode[] = []; + const nodeMap = new Map(); + + graph.graph.forEach((node, pos) => { + let label: string; + + // 编码为唯一哈希值(用字符串就行,V8 会自动帮你算哈希) + if (node.type === 'branch') { + label = `B:${node.tile}`; + } else { + label = `R:${node.resourceType}`; + } + + const wlNode: WLNode = { + originalPos: pos, + originalLabel: label, + currentLabel: label, + neighbors: [] + }; + nodeMap.set(pos, wlNode); + nodes.push(wlNode); + }); + + // 映射邻居节点 + nodes.forEach(node => { + const ginkaNode = graph.graph.get(node.originalPos); + ginkaNode?.neighbor.forEach(v => { + const wl = nodeMap.get(v); + if (wl) node.neighbors.push(wl); + }); + }); + + return nodes; +} + +function weisfeilerLehmanIteration( + nodes: WLNode[], + iterations: number, + decay: number = 0.6 // 衰减权重,减小长距离图的权重 +) { + const labelHistory: string[][] = []; + + for (let i = 0; i < iterations; i++) { + const newLabels: string[] = []; + + // 生成新标签 + nodes.forEach(node => { + const neighborLabels = node.neighbors + .map(n => n.currentLabel) + .sort(); + const compositeLabel = `${node.currentLabel}|${neighborLabels.join( + ',' + )}`; + + newLabels.push(compositeLabel); + }); + + // 更新节点标签并记录 + nodes.forEach((node, idx) => { + node.currentLabel = newLabels[idx]; + }); + labelHistory.push([...newLabels]); + } + + // 统计每个节点的数量 + let weight = 1; + const numMap = new Map(); + labelHistory.forEach(iter => { + iter.forEach(v => { + if (!numMap.has(v)) { + numMap.set(v, weight); + } else { + numMap.set(v, numMap.get(v)! + weight); + } + }); + weight *= decay; + }); + // 把每个节点的原始标签也加上,权重使用最远权重,可以认为是资源重复率 + nodes.forEach(node => { + if (!numMap.has(node.originalLabel)) { + numMap.set(node.originalLabel, weight); + } else { + numMap.set( + node.originalLabel, + numMap.get(node.originalLabel)! + weight + ); + } + }); + + return numMap; +} + +function vectorizeFeatures(features: Map, vocab: string[]) { + const vec: number[] = new Array(vocab.length).fill(0); + + features.forEach((count, label) => { + const index = vocab.indexOf(label); + if (index !== -1) { + vec[index] += count; + } + }); + + return vec; +} + +function wlKernel( + graphA: GinkaGraph, + graphB: GinkaGraph, + iterations = 3 +): number { + // 编码节点 + const nodesA = encodeNodeLabels(graphA); + const nodesB = encodeNodeLabels(graphB); + + // 迭代生成标签 + const featuresA = weisfeilerLehmanIteration(nodesA, iterations); + const featuresB = weisfeilerLehmanIteration(nodesB, iterations); + + // 构建特征向量 + const vocab = [...new Set([...featuresA.keys(), ...featuresB.keys()])]; + const vecA = vectorizeFeatures(featuresA, vocab); + const vecB = vectorizeFeatures(featuresB, vocab); + + // 计算余弦相似度 + return cosineSimilarity(vecA, vecB); +} + +export function overallSimilarity( + a: GinkaTopologicalGraphs, + b: GinkaTopologicalGraphs +) { + // 使用 Weisfeiler-Lehman Kernel 方式计算拓扑图相似度 + const graphsA = a.graphs; + const graphsB = b.graphs; + + let totalSimilarity = 0; + const comparedGraph = new Set(); + graphsA.forEach(ga => { + let maxSimilarity = 0; + let maxGraph: GinkaGraph | null = null; + // 图之间两两比较,找到最接近的作为相似度 + for (const gb of graphsB) { + if (comparedGraph.has(gb)) continue; + // 计算迭代次数 + const min = Math.min(ga.graph.size, gb.graph.size); + const iterations = Math.ceil(Math.max(1, Math.log(min))); + const similarity = wlKernel(ga, gb, iterations); + if (similarity > maxSimilarity) { + maxSimilarity = similarity; + maxGraph = gb; + } + if (similarity === 1) break; + } + totalSimilarity += maxSimilarity; + if (maxGraph) comparedGraph.add(maxGraph); + }); + + // 取根号使结果更接近线性 + return Math.sqrt(totalSimilarity / graphsA.length); +} diff --git a/data/src/topology/test.ts b/data/src/topology/test.ts new file mode 100644 index 0000000..24d5ffb --- /dev/null +++ b/data/src/topology/test.ts @@ -0,0 +1,95 @@ +import { buildTopologicalGraph } from './graph'; +import { mirrorMapX, mirrorMapY, rotateMap } from './transform'; +import { overallSimilarity } from './similarity'; + +(() => { + // MT3 + const map1 = [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ]; + // MT6 + const map2 = [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ]; + // MT8 + // const map2 = [ + // [1, 1, 1, 1, 1, 1, 1], + // [1, 5, 8, 10, 7, 2, 1], + // [1, 2, 1, 5, 1, 7, 1], + // [1, 3, 1, 3, 6, 4, 1], + // [1, 6, 1, 6, 1, 8, 1], + // [1, 10, 7, 5, 1, 5, 1], + // [1, 1, 1, 1, 1, 1, 1] + // ]; + // MT3 微调 + // const map2 = [ + // [1, 1, 1, 1, 1, 1, 1], + // [1, 3, 1, 10, 7, 3, 1], + // [1, 6, 1, 5, 1, 7, 1], + // [1, 9, 1, 8, 1, 6, 1], + // [1, 5, 8, 0, 1, 7, 1], + // [1, 2, 1, 4, 7, 10, 1], + // [1, 1, 1, 1, 1, 1, 1] + // ]; + + // 1. 两张图与自身对比 + const graph1 = buildTopologicalGraph(map1); + const graph2 = buildTopologicalGraph(map2); + + console.log(`map1 vs map1: ${overallSimilarity(graph1, graph1)}`); + console.log(`map2 vs map2: ${overallSimilarity(graph2, graph2)}`); + + // 2. 两张图相互对比 + console.log(`map1 vs map2: ${overallSimilarity(graph1, graph2)}`); + console.log(`map2 vs map1: ${overallSimilarity(graph2, graph1)}`); + + // 3. x镜像对比 + const xFlipped1 = mirrorMapX(map1); + const xFlipped2 = mirrorMapX(map2); + const graphX1 = buildTopologicalGraph(xFlipped1); + const graphX2 = buildTopologicalGraph(xFlipped2); + console.log(`map1:x vs map1: ${overallSimilarity(graphX1, graph1)}`); + console.log(`map1:x vs map2: ${overallSimilarity(graphX1, graph2)}`); + console.log(`map1 vs map2:x: ${overallSimilarity(graph1, graphX2)}`); + console.log(`map2:x vs map2: ${overallSimilarity(graphX2, graph2)}`); + console.log(`map2:x vs map1: ${overallSimilarity(graphX2, graph2)}`); + console.log(`map2 vs map1:x: ${overallSimilarity(graph2, graphX1)}`); + + // 4. y镜像对比 + const yFlipped1 = mirrorMapY(map1); + const yFlipped2 = mirrorMapY(map2); + const graphY1 = buildTopologicalGraph(yFlipped1); + const graphY2 = buildTopologicalGraph(yFlipped2); + console.log(`map1:y vs map1: ${overallSimilarity(graphY1, graph1)}`); + console.log(`map1:y vs map2: ${overallSimilarity(graphY1, graph2)}`); + console.log(`map1 vs map2:y: ${overallSimilarity(graph1, graphY2)}`); + console.log(`map2:y vs map2: ${overallSimilarity(graphY2, graph2)}`); + console.log(`map2:y vs map1: ${overallSimilarity(graphY2, graph1)}`); + console.log(`map2 vs map1:y: ${overallSimilarity(graph2, graphY1)}`); + + // 5. xy 镜像混合对比 + console.log(`map1:x vs map1:y: ${overallSimilarity(graphX1, graphY1)}`); + console.log(`map1:y vs map2:x: ${overallSimilarity(graphY1, graphX2)}`); + console.log(`map1:x vs map2:x: ${overallSimilarity(graphX1, graphX2)}`); + console.log(`map1:x vs map2:y: ${overallSimilarity(graphX1, graphY2)}`); + + // 6. 旋转对比 + const rot901 = rotateMap(map1); + const rot902 = rotateMap(map2); + const graph901 = buildTopologicalGraph(rot901); + const graph902 = buildTopologicalGraph(rot902); + console.log(`map1:90 vs map1: ${overallSimilarity(graph1, graph901)}`); + console.log(`map2:90 vs map2: ${overallSimilarity(graph2, graph902)}`); +})(); diff --git a/data/src/topology/transform.ts b/data/src/topology/transform.ts new file mode 100644 index 0000000..9afef83 --- /dev/null +++ b/data/src/topology/transform.ts @@ -0,0 +1,13 @@ +export function mirrorMapX(map: number[][]) { + return map.map(v => [...v].reverse()); +} + +export function mirrorMapY(map: number[][]) { + return [...map].reverse(); +} + +export function rotateMap(map: number[][]) { + return [ + ...map[0].map((_, colIndex) => map.map(row => row[colIndex])) + ].reverse(); +} diff --git a/data/src/utils.ts b/data/src/utils.ts new file mode 100644 index 0000000..e575f00 --- /dev/null +++ b/data/src/utils.ts @@ -0,0 +1,40 @@ +interface DatasetMergable { + datasetId: number; + data: Record; +} + +export function mergeDataset( + ...datasets: DatasetMergable[] +): DatasetMergable { + const data: Record = {}; + datasets.forEach(v => { + for (const [key, value] of Object.entries(v.data)) { + const dataKey = `${v.datasetId}/${key}`; + data[dataKey] = value; + } + }); + + const dataset: DatasetMergable = { + datasetId: Math.floor(Math.random() * 1e12), + data: data + }; + + return dataset; +} + +export function cosineSimilarity(vecA: number[], vecB: number[]): number { + if (vecA.length !== vecB.length) { + throw new Error('Vectors must have same dimension'); + } + + let dot = 0, + normA = 0, + normB = 0; + for (let i = 0; i < vecA.length; i++) { + dot += vecA[i] * vecB[i]; + normA += vecA[i] ** 2; + normB += vecB[i] ** 2; + } + + return dot / (Math.sqrt(normA) * Math.sqrt(normB)); +} diff --git a/data/src/vision/similarity.ts b/data/src/vision/similarity.ts new file mode 100644 index 0000000..85e131b --- /dev/null +++ b/data/src/vision/similarity.ts @@ -0,0 +1,144 @@ +interface VisualSimilarityConfig { + // 类型重要性权重(需根据游戏设定调整) + typeWeights: { [key: number]: number }; + // 是否启用视觉焦点增强 + enableVisualFocus: boolean; + // 是否启用密度感知 + enableDensityAwareness: boolean; +} + +const DEFAULT_CONFIG: VisualSimilarityConfig = { + typeWeights: { + 0: 0.2, // 空地 + 1: 0.3, // 墙壁 + 2: 0.6, // 钥匙 + 3: 0.7, // 红宝石 + 4: 0.7, // 蓝宝石 + 5: 0.5, // 血瓶 + 6: 0.4, // 门 + 7: 0.5, // 弱怪 + 8: 0.6, // 中怪 + 9: 0.6, // 强怪 + 10: 0.4, // 楼梯 + 11: 0.4 // 箭头 + }, + enableVisualFocus: true, + enableDensityAwareness: true +}; + +export function calculateVisualSimilarity( + map1: number[][], + map2: number[][], + config = DEFAULT_CONFIG +): number { + // 尺寸校验 + if (map1.length !== map2.length || map1[0]?.length !== map2[0]?.length) { + return 0; // 或抛出异常 + } + + const rows = map1.length; + const cols = map1[0].length; + let totalScore = 0; + let maxPossibleScore = 0; + + // 视觉焦点权重图 + const focusWeights = config.enableVisualFocus + ? generateFocusWeights(rows, cols) + : Array(rows) + .fill(1) + .map(() => Array(cols).fill(1)); + + // 类型密度分布计算 + const densityMap = config.enableDensityAwareness + ? calculateDensityImpact(map1, map2, config.typeWeights) + : Array(rows) + .fill(1) + .map(() => Array(cols).fill(1)); + + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + const type1 = map1[i][j]; + const type2 = map2[i][j]; + + // 基础类型权重 + const baseWeight = Math.max( + config.typeWeights[type1] || 0.5, + config.typeWeights[type2] || 0.5 + ); + + // 空间权重组合 + const spatialWeight = focusWeights[i][j] * densityMap[i][j]; + + // 类型匹配得分 + const typeScore = type1 === type2 ? 1 : 0; + + totalScore += typeScore * baseWeight * spatialWeight; + maxPossibleScore += baseWeight * spatialWeight; + } + } + + return maxPossibleScore > 0 ? totalScore / maxPossibleScore : 0; +} + +/** + * 生成视觉焦点权重图(基于人类视觉注意力分布) + */ +function generateFocusWeights(rows: number, cols: number): number[][] { + const weights = []; + const centerX = cols / 2; + const centerY = rows / 2; + const maxDist = Math.sqrt(centerX ** 2 + centerY ** 2) * 0.7; + + for (let i = 0; i < rows; i++) { + const rowWeights = []; + for (let j = 0; j < cols; j++) { + // 使用高斯分布模拟视觉焦点 + const dx = (j - centerX) / cols; + const dy = (i - centerY) / rows; + const distance = Math.sqrt(dx ** 2 + dy ** 2); + const gaussian = Math.exp(-(distance ** 2) / (2 * 0.3 ** 2)); + rowWeights.push(1.0 + 0.6 * gaussian); // 中心区域最高1.6倍权重 + } + weights.push(rowWeights); + } + return weights; +} + +/** + * 计算类型密度影响权重 + */ +function calculateDensityImpact( + map1: number[][], + map2: number[][], + typeWeights: { [key: number]: number } +): number[][] { + const rows = map1.length; + const cols = map1[0].length; + const densityMap = Array(rows) + .fill(0) + .map(() => Array(cols).fill(0)); + + // 滑动窗口分析局部密度 + const windowSize = 3; + const halfWindow = Math.floor(windowSize / 2); + + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + let density = 0; + for (let di = -halfWindow; di <= halfWindow; di++) { + for (let dj = -halfWindow; dj <= halfWindow; dj++) { + const ni = i + di; + const nj = j + dj; + if (ni >= 0 && ni < rows && nj >= 0 && nj < cols) { + const weight1 = typeWeights[map1[ni][nj]] || 0.5; + const weight2 = typeWeights[map2[ni][nj]] || 0.5; + density += (weight1 + weight2) / 2; + } + } + } + // 密度权重:高密度区域增强对比度 + densityMap[i][j] = 1.0 + 0.4 * (density / windowSize ** 2); + } + } + return densityMap; +} diff --git a/data/src/vision/test.ts b/data/src/vision/test.ts new file mode 100644 index 0000000..388d355 --- /dev/null +++ b/data/src/vision/test.ts @@ -0,0 +1,74 @@ +import { calculateVisualSimilarity } from './similarity'; + +(() => { + // MT3 + const map1 = [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ]; + // MT6 + const map2 = [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ]; + // MT8 + const map3 = [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ]; + // MT3 微调 + const map4 = [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 4, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ]; + // MT10 + const map5 = [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ]; + + // 测试自我对比 + console.log(`map1 vs map1: ${calculateVisualSimilarity(map1, map1)}`); + console.log(`map2 vs map2: ${calculateVisualSimilarity(map2, map2)}`); + console.log(`map3 vs map3: ${calculateVisualSimilarity(map3, map3)}`); + console.log(`map4 vs map4: ${calculateVisualSimilarity(map4, map4)}`); + // 两两测试 + console.log(`map1 vs map2: ${calculateVisualSimilarity(map1, map2)}`); + console.log(`map1 vs map3: ${calculateVisualSimilarity(map1, map3)}`); + console.log(`map1 vs map4: ${calculateVisualSimilarity(map1, map4)}`); + console.log(`map1 vs map5: ${calculateVisualSimilarity(map1, map5)}`); + console.log(`map2 vs map3: ${calculateVisualSimilarity(map2, map3)}`); + console.log(`map2 vs map4: ${calculateVisualSimilarity(map2, map4)}`); + console.log(`map2 vs map5: ${calculateVisualSimilarity(map2, map5)}`); + console.log(`map3 vs map4: ${calculateVisualSimilarity(map3, map4)}`); + console.log(`map3 vs map5: ${calculateVisualSimilarity(map3, map5)}`); + console.log(`map4 vs map5: ${calculateVisualSimilarity(map4, map5)}`); + // 测试交换性 + console.log(`map2 vs map1: ${calculateVisualSimilarity(map2, map1)}`); + console.log(`map4 vs map2: ${calculateVisualSimilarity(map4, map2)}`); +})(); diff --git a/data/tsconfig.json b/data/tsconfig.json new file mode 100644 index 0000000..92154f0 --- /dev/null +++ b/data/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "strict": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext"], + "skipLibCheck": true, + "noEmit": true, + "baseUrl": "." + }, + "include": ["src/**/*.ts", "src/**/*.d.ts"] +} diff --git a/data/vitest.config.ts b/data/vitest.config.ts new file mode 100644 index 0000000..2af7cfb --- /dev/null +++ b/data/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node' + } +}); diff --git a/dataset.json b/dataset.json new file mode 100644 index 0000000..6232d5f --- /dev/null +++ b/dataset.json @@ -0,0 +1,175 @@ +{ + "datasetId": 468713377296, + "data": { + "93529307366/MT1": { + "text": [ + "左右对称结构,右侧包含一个红宝石,左侧包含一个蓝宝石,怪物全部是中等强度", + "画一个左右对称,怪物强度中等,需要开门才能上楼的地图", + "生成一个有两把黄钥匙,三个黄门,四个血瓶,四个怪物的左右对称地图" + ], + "map": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7] + }, + "93529307366/MT2": { + "text": [ + "画一个主干道上只有弱怪,没有宝石,包含咸鱼门的地图", + "宝石被中等怪物守护,也可以通过开咸鱼门获得,以弱怪为主的地图", + "这是一个主干道在地图边缘的地图,可以考虑开一个黄门提前获得宝石" + ], + "map": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7] + }, + "93529307366/MT3": { + "text": [ + "一个红宝石在黄门里面,门前有一个强怪守护,另一个红宝石可以打败一个中怪和两个弱怪后获得", + "画一个主干道在中间,左侧有一个强怪守护的红宝石,右侧有一个红宝石的地图", + "生成一个左侧怪物较强,右侧怪物较弱的地图,右侧有一个黄门捷径" + ], + "map": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7] + }, + "93529307366/MT4": { + "text": [ + "生成一个左右异形对称,包含各种强度的怪物,有中怪守护红宝石,强怪守护蓝宝石的地图", + "防御宝石由强怪守护,攻击宝石由中怪守护,左右不完全对称的地图", + "画一个可以通过开门绕过一个中等强度怪物和两个弱怪,包含三个血瓶,攻防宝石各一个的地图" + ], + "map": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7] + }, + "93529307366/MT5": { + "text": [ + "画一个左上右下对称,中间全是门和宝石,周围全是怪物和血瓶的地图", + "中间的门构成一个叉号状,空余位置填充宝石,外围包含怪物和血瓶,几乎没有空地的地图", + "生成一个偏资源向的楼层,有很多资源集中在中间,但是需要开门才能获得,地图外围有怪物挡路" + ], + "map": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7] + }, + "93529307366/MT6": { + "text": [ + "上楼梯在蓝门里面,需要开门才能上楼,怪物强度比较高,没有弱怪,宝石全由强怪守护,但是其中一个也可以咸门获得", + "画一个接近左右对称的地图,最好能好看点", + "生成一个有一把黄钥匙,怪物强度高的地图" + ], + "map": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7] + }, + "93529307366/MT7": { + "text": [ + "画一个几乎全由怪物组成,没有宝石,只有血瓶钥匙和门的地图", + "怪物强度高,但是血瓶数量多", + "可以直接开门上楼,没有宝石的地图" + ], + "map": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7] + }, + "93529307366/MT8": { + "text": [ + "以走廊为主,没有强怪,只有弱怪和中怪,有一定数量的宝石的地图", + "画一个蓝海风,以走廊为主的地图", + "生成一个资源比较丰富,有宝石、血瓶,需要开门才能上楼,有一些咸鱼门的地图" + ], + "map": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7] + }, + "93529307366/MT9": { + "text": [ + "左右基本对称,但是入口不对称,宝石种类不对称的地图", + "画一个左右基本对称,没有强怪的地图", + "生成一个有两个红宝石,一个蓝宝石,很多血瓶,没有强怪的地图" + ], + "map": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7] + }, + "93529307366/MT10": { + "text": [ + "左右对称的 Boss 层", + "画一个 Boss 层,入口在上方,Boss 在下方,中间有几个弱怪挡路", + "生成一个 Boss 层,可以咸门提前拿资源" + ], + "map": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7] + } + } +} diff --git a/ginka/__init__.py b/ginka/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ginka/dataset.py b/ginka/dataset.py new file mode 100644 index 0000000..d89a8b1 --- /dev/null +++ b/ginka/dataset.py @@ -0,0 +1,54 @@ +import json +import random +import torch +from torch.utils.data import Dataset +from transformers import BertTokenizer + +def load_data(path: str): + with open(path, 'r', encoding="utf-8") as f: + data = json.load(f) + + data_list = [] + for value in data["data"].values(): + data_list.append(value) + + return data_list + +class GinkaDataset(Dataset): + def __init__(self, data_path: str, tokenizer: BertTokenizer, max_len=128): + self.data = load_data(data_path) # 自定义数据加载函数 + self.tokenizer = tokenizer + self.max_len = max_len + self.max_size = 32 + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + item = self.data[idx] + + # 文本处理 + text = random.choice(item["text"]) + encoding = self.tokenizer( + text, + max_length=self.max_len, + padding="max_length", + truncation=True, + return_tensors="pt" + ) + + # 噪声生成 + w, h = item["size"] + noise = torch.randn(h, w, 1) + + # 目标矩阵填充 + target = torch.full((self.max_size, self.max_size), -100) # 使用-100忽略填充区域 + target[:h, :w] = torch.tensor(item["map"]) + + return { + "noise": noise, + "input_ids": encoding["input_ids"].squeeze(), + "attention_mask": encoding["attention_mask"].squeeze(), + "map_size": torch.tensor([h, w]), + "target": target + } \ No newline at end of file diff --git a/ginka/model/loss.py b/ginka/model/loss.py new file mode 100644 index 0000000..f87abb5 --- /dev/null +++ b/ginka/model/loss.py @@ -0,0 +1,338 @@ +import math +import torch +import torch.nn as nn +import torch.nn.functional as F +from pytorch_toolbelt import losses as L + +def wall_border_loss(pred: torch.Tensor, probs: torch.Tensor, allow_border=[1, 11]): + """地图最外层是否为墙""" + # 计算 softmax 概率 + B, C, H, W = pred.shape + + # 构造一个 [H, W] 的布尔 mask,选取最外圈的像素 + border_mask = torch.zeros((H, W), dtype=torch.bool, device=pred.device) + border_mask[0, :] = True + border_mask[-1, :] = True + border_mask[:, 0] = True + border_mask[:, -1] = True + + # 对允许的类别求概率和(即该像素为允许类别的总概率) + allowed_prob = probs[:, allow_border, :, :].sum(dim=1) # [B, H, W] + + # 只计算边界区域的损失:对于边界上的每个像素,要求 allowed_prob 越高越好 + border_allowed_prob = allowed_prob[:, border_mask] # [B, N_border_pixels] + + # 损失为 -log(allowed_prob) + loss = 1 - border_allowed_prob.mean() + + return loss + +def internal_wall_loss(logits, probs, wall_class=1, threshold=2.5): + """ + 针对内部区域(排除最外圈)设计的损失函数: + 当内部任意 2×2 区域的 wall 类别概率之和超过阈值时,施加惩罚。 + + 参数: + logits: 模型输出,形状 [B, C, H, W] + wall_class: 对应墙壁的类别索引(这里假设墙壁数字为1) + threshold: 2×2 区域概率之和的阈值,超过此值时施加惩罚。可根据实际情况调节。 + + 返回: + loss: 内部墙壁连续区域的平均惩罚损失 + """ + # 取出对应墙壁类别的概率图 [B, H, W] + wall_probs = probs[:, wall_class, :, :] + + # 排除最外圈,取内部区域 (H, W 均减去2) + interior = wall_probs[:, 1:-1, 1:-1] # [B, H-2, W-2] + + # 构造一个 2×2 的卷积核,全为 1,用于检测局部连续墙壁的概率之和 + kernel = torch.ones((1, 1, 2, 2), device=logits.device) + + # 对内部区域进行卷积操作,计算每个 2×2 区域内的概率和 + # 需要将 interior 扩展一个通道维度 + conv_result = F.conv2d(interior.unsqueeze(1), kernel, stride=1, padding=0) + # conv_result 的形状为 [B, 1, H-3, W-3] + + # 对于每个 2×2 区域,如果概率和超过 threshold,则产生惩罚 + # 这里采用 ReLU 计算超出部分,确保损失为非负 + penalty = F.relu(conv_result - threshold) + + # 取平均作为损失值 + loss = penalty.mean() + return loss + +def entrance_loss(logits, probs, stairs_class=10, arrow_class=11): + """ + 针对地图生成的额外约束损失: + - 保证最外圈不出现楼梯类型入口(数字10) + - 保证内部区域不出现箭头类型入口(数字11) + + 参数: + logits: 模型输出,形状 [B, C, H, W] + stairs_class: 楼梯入口对应的类别(数字10) + arrow_class: 箭头入口对应的类别(数字11) + + 返回: + loss: 针对入口出现的惩罚损失 + """ + # 先将 logits 转为概率分布 + B, C, H, W = logits.shape + + # 构造最外圈 mask:外圈为 True,其余为 False + outer_mask = torch.zeros((H, W), dtype=torch.bool, device=logits.device) + outer_mask[0, :] = True + outer_mask[-1, :] = True + outer_mask[:, 0] = True + outer_mask[:, -1] = True + + # 内部区域 mask + interior_mask = ~outer_mask # 取反 + + # 提取对应类别的概率图 + stairs_probs = probs[:, stairs_class, :, :] # 楼梯概率 [B, H, W] + arrow_probs = probs[:, arrow_class, :, :] # 箭头概率 [B, H, W] + + # 从最外圈提取楼梯概率;用 mask 索引时:张量[:, mask] 会将每个样本的外圈像素展平 + outer_stairs = stairs_probs[:, outer_mask] # [B, num_outer_pixels] + # 从内部区域提取箭头概率 + interior_arrow = arrow_probs[:, interior_mask] # [B, num_interior_pixels] + + # 损失设计:使得这些概率尽量接近 0,直接使用均值惩罚 + outer_loss = outer_stairs.mean() + interior_loss = interior_arrow.mean() + + total_loss = outer_loss + interior_loss + return total_loss + +def entrance_distance_and_presence_loss( + logits, probs, + arrow_class=11, stairs_class=10, + arrow_min_threshold=0.5, stairs_min_threshold=0.5, + lambda_arrow_presence=1.0, lambda_stairs_presence=1.0 +): + """ + 入口损失同时考虑: + 1. 局部距离约束:防止同一类型入口过于靠近 + 2. 存在性约束:鼓励至少放置一个入口 + + 箭头入口要求局部 (9x9) 内最多只有一个入口; + 楼梯入口要求在一个窗口(地图尺寸一半)内只出现一个楼梯入口。 + + 参数: + logits: 模型输出, shape [B, C, H, W] + arrow_class: 箭头入口类别(默认 11) + stairs_class: 楼梯入口类别(默认 10) + arrow_min_threshold: 箭头入口全局最小平均概率要求(可根据任务调节) + stairs_min_threshold: 楼梯入口全局最小平均概率要求 + lambda_arrow_presence: 箭头入口存在性损失权重 + lambda_stairs_presence: 楼梯入口存在性损失权重 + 返回: + total_loss: 综合入口距离与存在性损失 + """ + # 将 logits 转换为概率分布 + probs = F.softmax(logits, dim=1) # [B, C, H, W] + B, C, H, W = logits.shape + + # 提取箭头和楼梯的概率图 + arrow_probs = probs[:, arrow_class, :, :] # [B, H, W] + stairs_probs = probs[:, stairs_class, :, :] # [B, H, W] + + #### 局部距离约束 #### + # 箭头:构造 9x9 卷积核,半径 4 + kernel_arrow = torch.ones((1, 1, 9, 9), device=logits.device) + local_arrow_sum = F.conv2d(arrow_probs.unsqueeze(1), kernel_arrow, padding=4) + # 减去自身概率,计算多余的局部累积 + arrow_excess = local_arrow_sum - arrow_probs.unsqueeze(1) + arrow_distance_loss = F.relu(arrow_excess).mean() + + # 楼梯:使用窗口大小为 (W//2, H//2) + kernel_size_stairs = (max(1, W // 2), max(1, H // 2)) + kernel_stairs = torch.ones((1, 1, kernel_size_stairs[0], kernel_size_stairs[1]), device=logits.device) + pad_stairs = (kernel_size_stairs[0] // 2, kernel_size_stairs[1] // 2) + local_stairs_sum = F.conv2d(stairs_probs.unsqueeze(1), kernel_stairs, padding=pad_stairs) + stairs_excess = local_stairs_sum - stairs_probs.unsqueeze(1) + stairs_distance_loss = F.relu(stairs_excess).mean() + + #### 存在性约束 #### + # 计算每个样本中箭头的最大概率 + global_arrow_max = arrow_probs.view(B, -1).max(dim=1)[0] # [B] + global_stairs_max = stairs_probs.view(B, -1).max(dim=1)[0] # [B] + + # 取 batch 平均(或者你可以对每个样本分别计算损失再求平均) + global_arrow_max = global_arrow_max.mean() + global_stairs_max = global_stairs_max.mean() + + # 如果全局均值低于预期阈值,则施加额外惩罚 + arrow_presence_loss = F.relu(arrow_min_threshold - global_arrow_max) + stairs_presence_loss = F.relu(stairs_min_threshold - global_stairs_max) + + ap_weighted = lambda_arrow_presence * arrow_presence_loss + sp_weighted = lambda_stairs_presence * stairs_presence_loss + + # 总入口损失:局部距离约束 + 存在性约束(加权) + total_loss = arrow_distance_loss + stairs_distance_loss \ + + min(ap_weighted, sp_weighted) + return total_loss + +def monster_consecutive_loss(logits, probs, monster_classes=[7,8,9], threshold=2.9): + """ + 检查横向和纵向是否存在连续超过三个的怪物(类别 7,8,9)。 + + 参数: + logits: 模型输出,形状 [B, C, H, W] + monster_classes: 待检测的怪物类别列表 + threshold: 滑动窗口内概率和的阈值,若超过则施加惩罚 + (对于连续三个像素,如果每个像素概率接近 1,则窗口和接近 3) + + 返回: + loss: 惩罚损失(数值越高表示连续怪物区域越严重) + """ + # 将 logits 转换为概率分布 + B, C, H, W = logits.shape + + # 得到怪物整体概率图:将类别 7,8,9 的概率相加 + monster_probs = probs[:, monster_classes, :].sum(dim=1) # [B, H, W] + + # 注意:monster_probs 越高说明该像素更有可能是怪物 + + # --- 横向检测 --- + # 构造一个 (1,3) 的卷积核,全 1 + kernel_horiz = torch.ones((1, 1, 1, 3), device=logits.device) + # 对 monster_probs 加一个 channel 维度,使形状为 [B, 1, H, W] + conv_horiz = F.conv2d(monster_probs.unsqueeze(1), kernel_horiz, padding=(0,1)) + # conv_horiz 的每个值表示相邻三个像素的怪物概率和 + + # --- 纵向检测 --- + # 构造一个 (3,1) 的卷积核,全 1 + kernel_vert = torch.ones((1, 1, 3, 1), device=logits.device) + conv_vert = F.conv2d(monster_probs.unsqueeze(1), kernel_vert, padding=(1,0)) + # conv_vert 的每个值表示垂直连续三个像素的怪物概率和 + + # 对两个方向的窗口,如果概率和超过阈值,则计算超出部分的惩罚 + penalty_horiz = F.relu(conv_horiz - threshold) + penalty_vert = F.relu(conv_vert - threshold) + + # 将两个方向的惩罚损失取平均(或者直接相加) + loss = penalty_horiz.mean() + penalty_vert.mean() + return loss + +def illegal_block_loss(logits ,probs, used_classes=12, mode='mean'): + """ + 对未使用类别(例如 12 ~ 31)的预测概率施加惩罚, + 鼓励模型输出仅集中在 0 ~ 11 上。 + + 参数: + logits: 模型输出,形状 [B, num_classes, H, W] + used_classes: 已经使用的类别数(例如 12 表示只使用 0-11) + mode: 'mean' 使用平均概率,或 'mse' 使用均方误差 + + 返回: + penalty: 标量惩罚损失 + """ + # 选取非法类别的概率(注意:这一步会得到非法图块在每个像素上的概率) + illegal_probs = probs[:, range(used_classes, 32), :, :] # [B, len(illegal_classes), H, W] + + # 我们可以将非法图块的概率在类别维度上求和,得到每个像素的非法激活值 + illegal_activation = illegal_probs.sum(dim=1) # [B, H, W] + + # 接下来我们计算整个图上非法激活的“数量” + # 例如,可以直接对整个 batch 内非法激活求和 + total_illegal = illegal_activation.sum() # 标量 + + # 计算损失值:使用负指数函数。注意如果非法激活很小,总损失接近 exp(0)=1 + loss = torch.sqrt(total_illegal) + return loss + +def integrated_count_loss(probs, target, class_list=[0,1,2,3,4,5,6,7,8,9], tolerance=0.5): + """ + 对每个类别分别计算数量匹配损失,再取平均。 + + 参数: + probs: 模型输出的概率,形状 [B, num_classes, H, W] + target: 真实标签,形状 [B, H, W],类别取值在 0 ~ 使用范围-1 内 + class_list: 需要计算的类别列表 + tolerance: 每个类别允许的相对误差(例如 0.15 表示 15%) + + 返回: + loss: 对每个类别数量匹配损失取平均后的标量 + """ + total_loss = 0.0 + count = 0 + B, C, H, W = probs.shape + + for cls in class_list: + # 预测数量:对于当前类别,所有像素的预测概率和 + pred_count = probs[:, cls, :, :].sum() + # 真实数量:统计 target 中属于当前类别的像素数量 + true_count = (target == cls).float().sum() + + if true_count == 0: + # 参考地图中不包含该类别,允许最多出现 (sqrt(地图尺寸) / 2) 个单位的概率输出 + cls_loss = F.relu(pred_count - math.sqrt(H * W) / 2) + else: + # 计算相对误差 + rel_error = torch.abs(pred_count - true_count) / (true_count) + cls_loss = F.relu(rel_error - tolerance) + + total_loss += cls_loss + count += 1 + + # 求平均每个类别的损失 + avg_loss = total_loss / count + return avg_loss + +class GinkaLoss(nn.Module): + def __init__(self, weight=[0.35, 0.1, 0.1, 0.1, 0.1, 0.05, 0.1, 0.1]): + """Ginka Model 损失函数部分 + + Args: + weight (list, optional): 每一个损失函数的权重,从第 0 项开始,依次是: + 1. 拓扑图损失 + 2. 外圈墙壁损失 + 3. 内层 2*2 墙壁损失 + 4. 要求外层只能有箭头,内层只能有楼梯的损失 + 5. 入口间距及存在性损失 + 6. 连续怪物损失 + 7. 非法图块损失 + 8. 怪物、道具、门数量损失 + """ + super().__init__() + self.weight = weight + self.dice = L.DiceLoss(mode='multiclass') + self.ce = nn.CrossEntropyLoss() + + def forward(self, pred, target): + probs = F.softmax(pred, dim=1) + # 拓扑结构损失 + # structure_loss = topology_loss(pred, target) + # 地图结构损失 + border_loss = wall_border_loss(pred, probs) + wall_loss = internal_wall_loss(pred, probs) + entry_loss = entrance_loss(pred, probs) + entry_dis_loss = entrance_distance_and_presence_loss(pred, probs) + enemy_loss = monster_consecutive_loss(pred, probs) + valid_block_loss = illegal_block_loss(pred, probs, used_classes=12, mode="mean") + count_loss = integrated_count_loss(probs, target) + + print( + # structure_loss.item(), + border_loss.item(), + wall_loss.item(), + entry_loss.item(), + entry_dis_loss.item(), + enemy_loss.item(), + valid_block_loss.item(), + count_loss.item() + ) + + return ( + # structure_loss * self.weight[0] + + border_loss * self.weight[1] + + wall_loss * self.weight[2] + + entry_loss * self.weight[3] + + entry_dis_loss * self.weight[4] + + enemy_loss * self.weight[5] + + valid_block_loss * self.weight[6] + + count_loss * self.weight[7] + ) \ No newline at end of file diff --git a/ginka/model/model.py b/ginka/model/model.py new file mode 100644 index 0000000..2aa4db8 --- /dev/null +++ b/ginka/model/model.py @@ -0,0 +1,180 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from transformers import BertModel +from ...shared.attention import CBAM, SpatialAttention +from .sample import HybridUpsample, FinalUpsample, GumbelSampler + +class ResidualBlock(nn.Module): + """残差块""" + def __init__(self, channels): + super().__init__() + self.conv = nn.Sequential( + nn.Conv2d(channels, channels, 3, padding=1), + nn.GroupNorm(8, channels), + nn.GELU(), + nn.Conv2d(channels, channels, 3, padding=1), + nn.GroupNorm(8, channels) + ) + + def forward(self, x): + return x + self.conv(x) + +class DynamicPadConv(nn.Module): + """支持动态处理奇数尺寸的智能卷积""" + def __init__(self, in_ch, out_ch, kernel=3, stride=1): + super().__init__() + self.conv = nn.Conv2d( + in_ch, out_ch, kernel, + stride=stride, + padding=kernel//2 + ) + self.requires_pad = (stride > 1) # 仅在下采样时需要填充 + + def forward(self, x): + if self.requires_pad: + # 动态计算各维度需要填充的量 + pad_h = x.size(-2) % 2 + pad_w = x.size(-1) % 2 + if pad_h or pad_w: + x = F.pad(x, (0, pad_w, 0, pad_h)) # 右下填充 + return self.conv(x) + +class ConditionInjector(nn.Module): + """基于注意力机制的条件注入""" + def __init__(self, cond_dim=128, feat_dim=256): + super().__init__() + self.cond_proj = nn.Sequential( + nn.Linear(cond_dim, feat_dim * 2), + nn.GELU(), + nn.LayerNorm(feat_dim * 2) + ) + self.channel_att = nn.Sequential( + nn.Conv2d(feat_dim, feat_dim//8, 1), + nn.GELU(), + nn.Conv2d(feat_dim//8, feat_dim, 1), + nn.Sigmoid() + ) + + def forward(self, x, cond): + # 投影条件向量 + gamma, beta = self.cond_proj(cond).chunk(2, dim=1) # [B, D] + + # 通道注意力调制 + att = self.channel_att(x) # [B, C, H, W] + modulated = x * att + + # 添加条件偏置 + return modulated + beta.view(-1, gamma.size(1), 1, 1) + +class GinkaEncoder(nn.Module): + """编码器(下采样)部分""" + def __init__(self, in_ch, out_ch): + super().__init__() + self.encoder = nn.Sequential( + DynamicPadConv(in_ch, out_ch, stride=1), + ResidualBlock(out_ch), + CBAM(out_ch), + nn.GroupNorm(8, out_ch), + nn.GELU() + ) + + def forward(self, x): + return self.encoder(x) + +class GinkaModel(nn.Module): + def __init__(self, in_ch=1, base_ch=64, num_classes=32): + """Ginka Model 模型定义部分 + + Args: + in_ch (int, optional): 输入通道数,默认是 1 + base_ch (int, optional): UNet 上下采样卷积基础通道数,默认 64 + num_classes (int, optional): 图块种类数量,默认 32 预留出一部分以供后续拓展功能 + """ + super().__init__() + + # 轻量级文本编码器(使用BERT前4层) + self.bert = BertModel.from_pretrained('google-bert/bert-base-chinese', output_hidden_states=True) + self.text_proj = nn.Linear(768, 128) + + # 动态尺寸处理系统 + self.size_embed = nn.Embedding(32, 16) # 处理最大32的尺寸 + + # 编码器 + self.enc1 = GinkaEncoder(in_ch, base_ch) + self.enc2 = GinkaEncoder(base_ch, base_ch * 2) + # self.enc3 = GinkaEncoder(base_ch * 2, base_ch * 4) + + # 中间层 + self.mid = nn.Sequential( + DynamicPadConv(base_ch * 2, base_ch * 4), + ConditionInjector(160, base_ch * 4) + ) + + # 解码器,解码器仅使用空间注意力 + self.dec1 = HybridUpsample(base_ch * 4, base_ch * 2) + self.dec1_att = SpatialAttention() + + self.dec2 = HybridUpsample(base_ch * 2, base_ch) + self.dec2_att = SpatialAttention() + + # self.dec3 = HybridUpsample(base_ch * 2, base_ch) + # self.dec3_att = SpatialAttention() + + # 输出层 + self.out = FinalUpsample(base_ch, num_classes) + + def forward(self, noise, input_ids, attention_mask, map_size): + """ + Args: + noise: 噪声输入 [BS, H, W, 1] + input_ids: 文本token id [BS, seq_len] + attention_mask: 文本attention mask [BS, seq_len] + map_size: 地图尺寸 [BS, 2] (height, width) + Returns: + logits: 输出logits [BS, num_classes, H, W] + """ + # 文本特征提取 + with torch.no_grad(): # 冻结BERT参数 + bert_outputs = self.bert( + input_ids=input_ids, + attention_mask=attention_mask, + output_hidden_states=True + ) + # 取前4层隐藏状态的平均 + hidden_states = torch.stack(bert_outputs.hidden_states[1:5]) # [4, BS, seq_len, 768] + text_features = torch.mean(hidden_states, dim=0)[:, 0, :] # [BS, 768] + text_features = self.text_proj(text_features) # [BS, 128] + + # 尺寸特征处理 + h_emb = self.size_embed(map_size[:, 0]) # [BS, 16] + w_emb = self.size_embed(map_size[:, 1]) # [BS, 16] + size_features = torch.cat([h_emb, w_emb], dim=1) # [BS, 32] + + # 特征融合 + conditional = torch.cat([text_features, size_features], dim=1) # [BS, 160] + + # 调整噪声输入维度 + x = noise.permute(0, 3, 1, 2) # [BS, 1, H, W] + + # 编码器路径 + x1 = self.enc1(x) # [BS, 64, H / 2, W / 2] + x2 = self.enc2(x1) # [BS, 128, H / 4, W / 4] + + # 中间层(注入条件) + x_mid = self.mid[0](x2) # [BS, 256, H / 4, W / 4] + x_mid = self.mid[1](x_mid, conditional) + + # 解码器路径 + d1 = self.dec1(x_mid, x2) # [BS, 128, H / 2, W / 2] + d1 = self.dec1_att(d1) + d2 = self.dec2(d1, x1) # [BS, 64, H, W] + d2 = self.dec2_att(d2) + # d3 = self.dec3(d2, x1) + # d3 = self.dec3_att(d3) + + # 最终自适应上采样 + h, w = noise.shape[1:3] # 获取原始输入尺寸 + return self.out(d2, (h, w)) + + \ No newline at end of file diff --git a/ginka/model/sample.py b/ginka/model/sample.py new file mode 100644 index 0000000..d8e6e95 --- /dev/null +++ b/ginka/model/sample.py @@ -0,0 +1,79 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +import torchvision.ops as ops + +class HybridUpsample(nn.Module): + """自适应尺寸的混合上采样""" + def __init__(self, in_ch, out_ch, skip_ch=None): + super().__init__() + # 子像素卷积上采样 + self.subpixel = nn.Sequential( + nn.Conv2d(in_ch, out_ch * 4, 3, padding=1), + nn.PixelShuffle(2) # 2倍上采样 + ) + + # 跳跃连接处理 + self.skip_conv = nn.Conv2d(skip_ch, out_ch, 1) if skip_ch else None + self.adaptive_pool = nn.AdaptiveAvgPool2d(None) + + def forward(self, x, skip=None): + x = self.subpixel(x) # [B, out_ch, 2H, 2W] + + if skip is not None and self.skip_conv: + # 自动对齐尺寸 + if x.shape[-2:] != skip.shape[-2:]: + skip = F.interpolate(skip, size=x.shape[-2:], mode='nearest') + + # 融合特征 + x = x + self.skip_conv(skip) + + return x + +class DiscreteAwareUpsample(nn.Module): + """离散感知的智能上采样模块""" + def __init__(self, in_ch, out_ch, base_size=16): + super().__init__() + self.base_size = base_size + self.scale_factors = [2, 4, 8] # 支持放大倍数 + + # 可变形卷积增强几何感知 + self.deform_conv = ops.DeformConv2d(in_ch, in_ch, kernel_size=3, padding=1) + + # 多尺度特征融合 + self.multi_scale = nn.ModuleList([ + nn.Sequential( + nn.Conv2d(in_ch, in_ch//4, 1), + nn.Upsample(scale_factor=s, mode='nearest') + ) for s in self.scale_factors + ]) + + # 门控上采样机制 + self.gate_conv = nn.Conv2d(in_ch*2, len(self.scale_factors)+1, 3, padding=1) + + # 离散化输出层 + self.final_conv = nn.Sequential( + nn.Conv2d(in_ch, out_ch*4, 3, padding=1), + nn.PixelShuffle(2), # 亚像素卷积 + nn.Conv2d(out_ch, out_ch, 3, padding=1) + ) + + def forward(self, x, target_size): + # 几何特征提取 + deform_feat = self.deform_conv(x) + + # 生成多尺度特征 + scale_features = [f(deform_feat) for f in self.multi_scale] + + # 动态门控选择 + gate_map = F.softmax(self.gate_conv(torch.cat([x, deform_feat], dim=1)), dim=1) + + # 加权融合多尺度特征 + combined = sum(g * F.interpolate(f, size=target_size, mode='nearest') + for g, f in zip(gate_map.unbind(1), scale_features+[x])) + + # 离散化上采样 + out = self.final_conv(combined) + + # 结构化约束(保持通道独立性) + return out.argmax(dim=1).unsqueeze(1).float() # 伪梯度保留 diff --git a/ginka/test/__init__.py b/ginka/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ginka/test/model.py b/ginka/test/model.py new file mode 100644 index 0000000..05dfa75 --- /dev/null +++ b/ginka/test/model.py @@ -0,0 +1,46 @@ +import torch +from ..model.model import DynamicPadConv, ConditionInjector, HybridUpsample + +def test_dynamic_conv(): + conv = DynamicPadConv(3, 64, stride=2) + + # 测试奇数尺寸 + x = torch.randn(1, 3, 15, 17) + out = conv(x) + assert out.shape == (1, 64, 8, 9), f"Got {out.shape}" + + # 测试偶数尺寸 + x = torch.randn(1, 3, 16, 16) + out = conv(x) + assert out.shape == (1, 64, 8, 8) + +def test_condition_injector(): + injector = ConditionInjector(128, 256) + x = torch.randn(2, 256, 16, 16) + cond = torch.randn(2, 128) + + out = injector(x, cond) + assert out.shape == x.shape + assert not torch.allclose(out, x) # 确保条件起作用 + +def test_hybrid_upsample(): + # 带跳跃连接的情况 + upsample = HybridUpsample(256, 128, skip_ch=64) + x = torch.randn(2, 256, 8, 8) + skip = torch.randn(2, 64, 16, 16) + out = upsample(x, skip) + assert out.shape == (2, 128, 16, 16) + + # 无跳跃连接的情况 + upsample = HybridUpsample(256, 128) + out = upsample(x) + assert out.shape == (2, 128, 16, 16) + +def test_all(): + test_dynamic_conv() + print("✅ 动态卷积测试完毕") + test_condition_injector() + print("✅ 条件注入测试完毕") + test_hybrid_upsample() + print("✅ 混合上采样测试完毕") + \ No newline at end of file diff --git a/ginka/test_model.py b/ginka/test_model.py new file mode 100644 index 0000000..2843c18 --- /dev/null +++ b/ginka/test_model.py @@ -0,0 +1,4 @@ +from .test.model import test_all + +if __name__ == "__main__": + test_all() \ No newline at end of file diff --git a/ginka/train.py b/ginka/train.py new file mode 100644 index 0000000..a8e9d2c --- /dev/null +++ b/ginka/train.py @@ -0,0 +1,113 @@ +import os +from datetime import datetime +import torch +import torch.optim as optim +import torch.nn.functional as F +from torch.utils.data import DataLoader +from transformers import BertTokenizer +from tqdm import tqdm +from .model.model import GinkaModel +from .model.loss import GinkaLoss +from .dataset import GinkaDataset + +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +os.makedirs("result", exist_ok=True) + +epochs = 100 + +def collate_fn(batch): + # 动态填充噪声到最大尺寸 + max_h = max([b["noise"].shape[0] for b in batch]) + max_w = max([b["noise"].shape[1] for b in batch]) + + padded_batch = {} + for key in ["noise", "target"]: + padded = [] + for b in batch: + tensor = b[key] + pad_h = max_h - tensor.shape[0] + pad_w = max_w - tensor.shape[1] + padded.append(F.pad(tensor, (0, pad_w, 0, pad_h), value=-100 if key=="target" else 0)) + padded_batch[key] = torch.stack(padded) + + # 其他字段直接堆叠 + for key in ["input_ids", "attention_mask", "map_size"]: + padded_batch[key] = torch.stack([b[key] for b in batch]) + + return padded_batch + +def train(): + print(f"Using {"cuda" if torch.cuda.is_available() else "cpu"} to train model.") + model = GinkaModel() + model.to(device) + + # 准备数据集 + tokenizer = BertTokenizer.from_pretrained('google-bert/bert-base-chinese') + dataset = GinkaDataset("F:/github-ai/ginka-generator/dataset.json", tokenizer) + dataloader = DataLoader( + dataset, + batch_size=4, + shuffle=True, + collate_fn=collate_fn, + num_workers=0 + ) + + # 设定优化器与调度器 + optimizer = optim.AdamW(model.parameters(), lr=3e-4) + scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs) + criterion = GinkaLoss() + + # 开始训练 + for epoch in tqdm(range(epochs)): + model.train() + total_loss = 0 + + # 温度退火 + model.gumbel.tau = max(0.1, 1.0 - 0.9 * epoch / epochs) + + for batch in dataloader: + # 数据迁移到设备 + noise = batch["noise"].to(device) + input_ids = batch["input_ids"].to(device) + attention_mask = batch["attention_mask"].to(device) + map_size = batch["map_size"].to(device) + target = batch["target"].to(device) + + # 前向传播 + optimizer.zero_grad() + outputs = model(noise, input_ids, attention_mask, map_size) + + print(torch.argmax(torch.softmax(outputs, dim=1), dim=1)) + # print(sampled[0, :, :, 1]) + + # 构建拓扑图 + # with torch.no_grad(): + # pred_graphs = build_topology_graph(outputs.argmax(1)) + # ref_graphs = build_topology_graph(target) + + # 计算损失 + loss = criterion( + outputs, # 调整为 [BS, C, H, W] + target + ) + + # 反向传播 + loss.backward() + optimizer.step() + total_loss += loss.item() + + tqdm.write(f"[INFO {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Epoch: {epoch} | loss: {total_loss:.6f} | lr: {(optimizer.param_groups[0]['lr']):.6f}") + + # 学习率调整 + scheduler.step() + + print("Train ended.") + + torch.save({ + "model_state": model.state_dict(), + "optimizer_state": optimizer.state_dict(), + }, f"result/ginka.pth") + +if __name__ == "__main__": + torch.set_num_threads(8) + train() diff --git a/minamo-dataset.json b/minamo-dataset.json new file mode 100644 index 0000000..d76b4c7 --- /dev/null +++ b/minamo-dataset.json @@ -0,0 +1,2673 @@ +{ + "datasetId": 918361849137, + "data": { + "927660773022/MT6:MT8": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.3017974109693317, + "visionSimilarity": 0.3949483834836556, + "size": [7, 7] + }, + "927660773022/MT6:MT6": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 1, + "size": [7, 7] + }, + "927660773022/MT8:MT8": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 1, + "size": [7, 7] + }, + "927660773022/MT8.0.2:MT6": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.3017974109693317, + "visionSimilarity": 0.4141881814998833, + "size": [7, 7] + }, + "927660773022/MT6:MT6.S0": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 10, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 3, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 2] + ], + "size": [7, 7], + "topoSimilarity": 0.5648603495010678, + "visionSimilarity": 0.9120472267815303 + }, + "927660773022/MT6:MT6.S1": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 0, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 0, 10, 1], + [1, 6, 0, 9, 1, 5, 1], + [1, 8, 0, 0, 0, 8, 1], + [1, 7, 1, 3, 1, 2, 9], + [10, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 5, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.6208051114234194, + "visionSimilarity": 0.7876948268735627 + }, + "927660773022/MT6:MT6.S2": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 6, 0, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.37173035004567245, + "visionSimilarity": 0.9101539842522579 + }, + "927660773022/MT6:MT6.S3": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 3, 3], + [1, 5, 1, 4, 1, 10, 1], + [1, 6, 6, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 7, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 11, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.36775239615454747, + "visionSimilarity": 0.8535076655591594 + }, + "927660773022/MT6:MT6.S4": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [5, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 5, 0, 6, 0, 8, 1], + [3, 8, 1, 10, 1, 3, 1], + [1, 9, 4, 1, 4, 9, 1], + [8, 1, 1, 7, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.6832827769570926, + "visionSimilarity": 0.7588323148498017 + }, + "927660773022/MT9:MT10": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.23425444000158943, + "visionSimilarity": 0.5313818202646722, + "size": [7, 7] + }, + "927660773022/MT9:MT9": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 1, + "size": [7, 7] + }, + "927660773022/MT9:MT9.S0": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 1, 3], + [1, 5, 7, 0, 3, 5, 1], + [1, 8, 5, 6, 6, 2, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 0], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.5153987413888277, + "visionSimilarity": 0.7880157550978304 + }, + "927660773022/MT9:MT9.S1": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 0, 1, 0, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 11, 8, 11, 1], + [5, 6, 6, 1, 1, 1, 1], + [1, 1, 1, 10, 1, 3, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.40212248555400054, + "visionSimilarity": 0.7376568068029206 + }, + "927660773022/MT1:MT10": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.19658650194614685, + "visionSimilarity": 0.43362423236370606, + "size": [7, 7] + }, + "927660773022/MT1:MT1": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 1, + "size": [7, 7] + }, + "927660773022/MT10:MT10": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 1, + "size": [7, 7] + }, + "927660773022/MT1:MT1.S0": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 6, 1, 2, 1], + [1, 6, 1, 2, 0, 8, 1], + [1, 4, 2, 6, 0, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [0, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 4, 1] + ], + "size": [7, 7], + "topoSimilarity": 1, + "visionSimilarity": 0.8001501821753071 + }, + "927660773022/MT1:MT1.S1": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 5, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 8], + [6, 4, 9, 2, 6, 8, 1], + [1, 7, 1, 2, 1, 5, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 5, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.772511528523379, + "visionSimilarity": 0.6226071587601213 + }, + "927660773022/MT1:MT1.S2": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.7287529314903132, + "visionSimilarity": 1 + }, + "927660773022/MT5:MT10": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.22536805248757746, + "visionSimilarity": 0.28616311155975555, + "size": [7, 7] + }, + "927660773022/MT5:MT5": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 1, + "size": [7, 7] + }, + "927660773022/MT5:MT5.S0": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 8, 9, 1, 1], + [1, 8, 7, 7, 0, 10, 1], + [1, 5, 6, 4, 3, 0, 11], + [1, 7, 3, 6, 4, 10, 1], + [1, 2, 1, 3, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 6, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.4573101892992204, + "visionSimilarity": 0.718715429071017 + }, + "927660773022/MT5:MT5.S1": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 8], + [3, 5, 5, 4, 6, 0, 1], + [1, 7, 1, 6, 3, 7, 1], + [1, 2, 6, 4, 7, 5, 1], + [10, 1, 2, 7, 5, 8, 1], + [10, 1, 8, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.28704468891875257, + "visionSimilarity": 0.7785782831565253 + }, + "927660773022/MT2:MT4": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.30948832551468947, + "visionSimilarity": 0.4915917893793176, + "size": [7, 7] + }, + "927660773022/MT2:MT2": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 1, + "size": [7, 7] + }, + "927660773022/MT4:MT4": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 1, + "size": [7, 7] + }, + "927660773022/MT2:MT2.S0": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 4, 5, 1, 1, 2, 1], + [1, 0, 7, 1, 0, 5, 1], + [1, 6, 1, 7, 8, 5, 1], + [1, 5, 1, 4, 1, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.20562777870387494, + "visionSimilarity": 0.7777165436056923 + }, + "927660773022/MT2:MT2.S1": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [8, 1, 11, 1, 1, 2, 1], + [8, 0, 7, 5, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 6, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 1, + "visionSimilarity": 0.842573737728365 + }, + "927660773022/MT2:MT2.S2": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [11, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.5115260363699541, + "visionSimilarity": 0.9808903090913788 + }, + "927660773022/MT2:MT2.S3": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [6, 5, 5, 11, 0, 2, 1], + [1, 1, 5, 1, 1, 7, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 7, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.7172158234273707, + "visionSimilarity": 0.831883427069396 + }, + "927660773022/MT7:MT9": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.23762238548385364, + "visionSimilarity": 0.3589203062751096, + "size": [7, 7] + }, + "927660773022/MT7:MT7": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 1, + "size": [7, 7] + }, + "927660773022/MT7:MT7.S0": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 1, 9, 5, 1], + [1, 8, 4, 10, 0, 5, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 9, 5, 0, 6, 4], + [1, 1, 1, 1, 4, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.6289196526084939, + "visionSimilarity": 0.8018232156673724 + }, + "927660773022/MT7:MT7.S1": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 0, 1, 1], + [1, 11, 9, 2, 9, 5, 3], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.46767418100529157, + "visionSimilarity": 0.9296009350571861 + }, + "927660773022/MT7:MT7.S2": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [6, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 4], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.8152460847093931, + "visionSimilarity": 0.947037588940459 + }, + "927660773022/MT7:MT7.S3": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 9, 7], + [1, 5, 9, 2, 3, 7, 0], + [1, 10, 9, 10, 0, 8, 1], + [5, 9, 9, 6, 9, 5, 1], + [4, 0, 8, 10, 6, 0, 10], + [1, 9, 1, 5, 0, 8, 1], + [1, 1, 0, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.34236113109162, + "visionSimilarity": 0.6010597362413994 + }, + "927660773022/MT4:MT6": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.354519107054048, + "visionSimilarity": 0.40547829388953077, + "size": [7, 7] + }, + "927660773022/MT4:MT4.S0": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 11, 1], + [5, 1, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 6, 1, 1], + [6, 4, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.9999999999999999, + "visionSimilarity": 0.8568691600926786 + }, + "927660773022/MT4:MT7": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.22419001042992245, + "visionSimilarity": 0.4182364156379367, + "size": [7, 7] + }, + "927660773022/MT4.0.3:MT7": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 9, 0, 8, 10, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 5, 7, 5, 1, 3, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 0, 7, 10, 7, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.22419001042992245, + "visionSimilarity": 0.35326443675072067, + "size": [7, 7] + }, + "927660773022/MT4:MT10": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.2207914143678778, + "visionSimilarity": 0.36584435611304467, + "size": [7, 7] + }, + "927660773022/MT10.0.2:MT4": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.2207914143678778, + "visionSimilarity": 0.3280223663343546, + "size": [7, 7] + }, + "927660773022/MT4:MT4.S1": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 2, 1], + [1, 4, 7, 10, 7, 0, 1], + [1, 11, 1, 6, 1, 8, 1], + [1, 10, 1, 5, 7, 5, 1], + [1, 3, 1, 6, 7, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 2, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.5482745154408448, + "visionSimilarity": 0.7769186492540868 + }, + "927660773022/MT8:MT10": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.24103519364023906, + "visionSimilarity": 0.37253171109006106, + "size": [7, 7] + }, + "927660773022/MT10.3.3:MT8": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 1, 4, 3, 1], + [1, 1, 7, 6, 5, 1, 1], + [1, 10, 7, 5, 9, 1, 1], + [1, 1, 7, 6, 5, 1, 1], + [1, 5, 6, 1, 4, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.24103519364023906, + "visionSimilarity": 0.3328723428854514, + "size": [7, 7] + }, + "927660773022/MT2:MT5": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.2384020810264115, + "visionSimilarity": 0.30641882667726744, + "size": [7, 7] + }, + "927660773022/MT6:MT10": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.3190084001291151, + "visionSimilarity": 0.36729577403636055, + "size": [7, 7] + }, + "927660773022/MT6.0.1:MT10": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 10, 1, 4, 6, 5, 1], + [1, 5, 1, 9, 1, 6, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 2, 1, 10, 1, 5, 1], + [1, 9, 4, 1, 3, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.3190084001291151, + "visionSimilarity": 0.37023804242437114, + "size": [7, 7] + }, + "927660773022/MT6.0.2:MT6": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 0.48451216414143333, + "size": [7, 7] + }, + "927660773022/MT1:MT8": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.41135721806906644, + "visionSimilarity": 0.44890106366769617, + "size": [7, 7] + }, + "927660773022/MT4:MT9": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.40929956765384223, + "visionSimilarity": 0.39300667872176975, + "size": [7, 7] + }, + "927660773022/MT4.3.1:MT9": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 3, 1, 10, 1], + [1, 7, 1, 1, 1, 8, 1], + [1, 10, 6, 5, 6, 0, 1], + [1, 7, 1, 7, 1, 9, 1], + [1, 0, 8, 5, 1, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.40929956765384223, + "visionSimilarity": 0.3901872293437052, + "size": [7, 7] + }, + "927660773022/MT4:MT8": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.3138216647734487, + "visionSimilarity": 0.5491211455672315, + "size": [7, 7] + }, + "927660773022/MT8.3.2:MT4": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 4, 7, 2, 1], + [1, 1, 1, 6, 1, 7, 1], + [1, 5, 6, 3, 5, 10, 1], + [1, 7, 1, 1, 1, 8, 1], + [1, 10, 6, 3, 2, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.3138216647734487, + "visionSimilarity": 0.4649127800363792, + "size": [7, 7] + }, + "927660773022/MT1:MT2": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.21160734089652974, + "visionSimilarity": 0.41486470481188403, + "size": [7, 7] + }, + "927660773022/MT2:MT6": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.2129170090357399, + "visionSimilarity": 0.3888225606633618, + "size": [7, 7] + }, + "927660773022/MT3:MT7": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.31564958876461413, + "visionSimilarity": 0.36269482634336153, + "size": [7, 7] + }, + "927660773022/MT3:MT3": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 1, + "size": [7, 7] + }, + "927660773022/MT3.2.3:MT3": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 1, + "size": [7, 7] + }, + "927660773022/MT2:MT8": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.3192640686139386, + "visionSimilarity": 0.4481364024777688, + "size": [7, 7] + }, + "927660773022/MT2:MT2.S4": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 5, 1, 1, 1, 11, 1], + [1, 1, 7, 10, 0, 2, 1], + [1, 1, 5, 3, 1, 7, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 3, 10, 1], + [1, 1, 1, 1, 4, 10, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.45353472859592436, + "visionSimilarity": 0.7283393415442388 + }, + "927660773022/MT6:MT9": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.23733759707919344, + "visionSimilarity": 0.4388567251775022, + "size": [7, 7] + }, + "927660773022/MT5:MT8": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.25315816748390524, + "visionSimilarity": 0.3016848458497979, + "size": [7, 7] + }, + "927660773022/MT5.3.3:MT8": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 10, 0, 7, 5, 8, 1], + [1, 0, 6, 3, 6, 5, 1], + [1, 7, 4, 6, 4, 7, 1], + [1, 5, 6, 3, 6, 2, 1], + [1, 8, 5, 7, 2, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.25315816748390524, + "visionSimilarity": 0.28550802370577166, + "size": [7, 7] + }, + "927660773022/MT5:MT5.S2": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 10, 1], + [1, 5, 8, 7, 0, 0, 1], + [1, 5, 6, 4, 6, 1, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 8, 1, 8], + [1, 5, 1, 1, 4, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.40222050217239663, + "visionSimilarity": 0.7581177930974216 + }, + "927660773022/MT5:MT5.S3": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.9999999999999999, + "visionSimilarity": 1 + }, + "927660773022/MT8:MT9": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.2631049734757305, + "visionSimilarity": 0.378390134371282, + "size": [7, 7] + }, + "927660773022/MT8:MT8.S0": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 2], + [1, 5, 8, 10, 7, 8, 1], + [1, 2, 1, 5, 1, 7, 2], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 5, 1, 8, 1], + [1, 10, 7, 6, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.6837702710640851, + "visionSimilarity": 0.8580844945203167 + }, + "927660773022/MT8:MT8.S1": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 10, 1, 1, 9], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 4, 1], + [1, 7, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 3, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.5910232819709086, + "visionSimilarity": 0.8631539662691041 + }, + "927660773022/MT8:MT8.S2": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 11, 1, 1, 1, 1], + [1, 5, 8, 10, 1, 2, 1], + [1, 2, 1, 5, 7, 7, 1], + [1, 3, 1, 3, 0, 4, 1], + [1, 6, 1, 6, 1, 8, 5], + [1, 10, 7, 5, 2, 1, 5], + [1, 1, 1, 1, 1, 1, 6] + ], + "size": [7, 7], + "topoSimilarity": 0.5513433765079506, + "visionSimilarity": 0.7798299469921026 + }, + "927660773022/MT1:MT5": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.21438361595548397, + "visionSimilarity": 0.36195603358993605, + "size": [7, 7] + }, + "927660773022/MT5.1.3:MT1": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 2, 6, 3, 6, 5, 1], + [1, 7, 4, 6, 4, 7, 1], + [1, 5, 6, 3, 6, 0, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.21438361595548397, + "visionSimilarity": 0.3623383246771316, + "size": [7, 7] + }, + "927660773022/MT1:MT1.S3": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 6, 1, 1, 1, 1, 1], + [1, 5, 1, 0, 1, 5, 1], + [0, 8, 0, 2, 0, 8, 1], + [1, 4, 0, 6, 5, 3, 3], + [1, 8, 1, 2, 6, 8, 1], + [7, 5, 6, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 5] + ], + "size": [7, 7], + "topoSimilarity": 0.37339996434394795, + "visionSimilarity": 0.7758813926690464 + }, + "927660773022/MT1:MT1.S4": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [10, 1, 1, 1, 1, 10, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 10, 0, 2, 0, 4, 1], + [1, 4, 1, 6, 6, 3, 9], + [1, 5, 6, 2, 6, 8, 1], + [1, 8, 3, 10, 3, 5, 1], + [1, 1, 3, 1, 9, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.6063151184401702, + "visionSimilarity": 0.6600286012298419 + }, + "927660773022/MT1:MT9": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.28387972920214055, + "visionSimilarity": 0.3440459626983058, + "size": [7, 7] + }, + "927660773022/MT5:MT9": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.1423081375013349, + "visionSimilarity": 0.3763133974796662, + "size": [7, 7] + }, + "927660773022/MT7:MT8": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.22360993598603532, + "visionSimilarity": 0.34823611686116446, + "size": [7, 7] + }, + "927660773022/MT2:MT3": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.37043448877500385, + "visionSimilarity": 0.4590241760200054, + "size": [7, 7] + }, + "927660773022/MT2:MT9": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.24741149009219096, + "visionSimilarity": 0.34635247375564543, + "size": [7, 7] + }, + "927660773022/MT2.1.1:MT9": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 10, 7, 5, 2, 7, 1], + [1, 8, 1, 1, 1, 0, 1], + [1, 4, 6, 0, 1, 10, 1], + [1, 1, 1, 7, 5, 7, 1], + [1, 5, 6, 0, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.24741149009219096, + "visionSimilarity": 0.3263336284583122, + "size": [7, 7] + }, + "927660773022/MT5:MT6": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.21897545951059205, + "visionSimilarity": 0.3317498830643538, + "size": [7, 7] + }, + "927660773022/MT6.1.1:MT6": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 9, 2, 8, 5, 10, 1], + [1, 4, 1, 0, 1, 1, 1], + [1, 1, 10, 6, 9, 4, 1], + [1, 3, 1, 0, 1, 6, 1], + [1, 9, 5, 8, 6, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 0.4392423791889623, + "size": [7, 7] + }, + "927660773022/MT2:MT10": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.3273339319310609, + "visionSimilarity": 0.3638973196798284, + "size": [7, 7] + }, + "927660773022/MT2.1.3:MT10": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 0, 1, 5, 1], + [1, 1, 1, 7, 5, 7, 1], + [1, 4, 6, 0, 1, 10, 1], + [1, 8, 1, 1, 1, 0, 1], + [1, 10, 7, 5, 2, 7, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.3273339319310609, + "visionSimilarity": 0.4183884727619406, + "size": [7, 7] + }, + "927660773022/MT2:MT7": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 0, 7, 1], + [1, 1, 5, 1, 1, 2, 1], + [1, 0, 7, 0, 1, 5, 1], + [1, 6, 1, 6, 1, 7, 1], + [1, 5, 1, 4, 8, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.2057653466089625, + "visionSimilarity": 0.35951700014057003, + "size": [7, 7] + }, + "927660773022/MT3:MT9": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 10, 1, 3, 1], + [1, 5, 7, 0, 7, 5, 1], + [1, 8, 6, 5, 6, 8, 1], + [1, 5, 8, 6, 8, 5, 1], + [1, 10, 5, 1, 5, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.33874478329104807, + "visionSimilarity": 0.429573191384092, + "size": [7, 7] + }, + "927660773022/MT3:MT3.S0": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 9, 7, 1, 1, 1, 1], + [1, 11, 1, 10, 7, 6, 1], + [1, 11, 10, 5, 6, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 9, 1, 9, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.6705711475577085, + "visionSimilarity": 0.764803054557559 + }, + "927660773022/MT3:MT3.S1": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 2, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 1, + "visionSimilarity": 0.9665620014502109 + }, + "927660773022/MT3:MT3.S2": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 0, 1, 1, 1, 7], + [0, 1, 3, 8, 7, 3, 1], + [1, 6, 8, 1, 1, 7, 1], + [1, 9, 1, 8, 1, 7, 1], + [1, 4, 8, 0, 1, 10, 10], + [1, 1, 2, 5, 5, 6, 1], + [1, 1, 2, 1, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.5404990038348193, + "visionSimilarity": 0.5573949388125659 + }, + "927660773022/MT3:MT3.S3": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [10, 3, 1, 10, 7, 8, 10], + [2, 5, 10, 5, 1, 7, 1], + [1, 9, 1, 0, 9, 3, 9], + [1, 5, 8, 0, 1, 7, 1], + [1, 5, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 5, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.4048714159848782, + "visionSimilarity": 0.6682929683203161 + }, + "927660773022/MT3:MT3.S4": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 0, 11, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 6, 5, 1, 7, 1], + [1, 9, 10, 11, 1, 6, 1], + [1, 5, 8, 0, 1, 1, 7], + [6, 2, 1, 1, 4, 10, 1], + [8, 1, 1, 5, 1, 1, 1] + ], + "size": [7, 7], + "topoSimilarity": 0.39377256150685663, + "visionSimilarity": 0.6973775469567552 + }, + "927660773022/MT7:MT10": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.19745156417294027, + "visionSimilarity": 0.33330032322210157, + "size": [7, 7] + }, + "927660773022/MT3:MT10": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 6, 7, 7, 7, 6, 1], + [1, 1, 6, 5, 6, 1, 1], + [1, 4, 5, 9, 5, 4, 1], + [1, 3, 1, 1, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.22970471758508645, + "visionSimilarity": 0.3701372466578907, + "size": [7, 7] + }, + "927660773022/MT3:MT5": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.2859149756529792, + "visionSimilarity": 0.28683574299388875, + "size": [7, 7] + }, + "927660773022/MT4:MT5": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.31724693806030013, + "visionSimilarity": 0.31001950082657104, + "size": [7, 7] + }, + "927660773022/MT5.2.2:MT5": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 10, 0, 7, 5, 8, 1], + [1, 0, 6, 4, 6, 5, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 5, 6, 4, 6, 2, 1], + [1, 8, 5, 7, 2, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 0.6707746700811702, + "size": [7, 7] + }, + "927660773022/MT4.0.3:MT5": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 9, 0, 8, 10, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 5, 7, 5, 1, 3, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 0, 7, 10, 7, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.31724693806030013, + "visionSimilarity": 0.3128336046912229, + "size": [7, 7] + }, + "927660773022/MT1:MT6": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.41387665225172354, + "visionSimilarity": 0.3721528524584664, + "size": [7, 7] + }, + "927660773022/MT1:MT7": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.22553888831770602, + "visionSimilarity": 0.45477772413783496, + "size": [7, 7] + }, + "927660773022/MT3:MT8": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.3246951233417888, + "visionSimilarity": 0.507809594014978, + "size": [7, 7] + }, + "927660773022/MT3.2.0:MT3": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 10, 7, 5, 1, 2, 1], + [1, 7, 1, 0, 8, 5, 1], + [1, 6, 1, 8, 1, 9, 1], + [1, 7, 1, 5, 1, 6, 1], + [1, 3, 7, 10, 1, 3, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 0.4145812267970579, + "size": [7, 7] + }, + "927660773022/MT8.0.3:MT8": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 5, 7, 10, 1], + [1, 8, 1, 6, 1, 6, 1], + [1, 4, 6, 3, 1, 3, 1], + [1, 7, 1, 5, 1, 2, 1], + [1, 2, 7, 10, 8, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 10, 7, 2, 1], + [1, 2, 1, 5, 1, 7, 1], + [1, 3, 1, 3, 6, 4, 1], + [1, 6, 1, 6, 1, 8, 1], + [1, 10, 7, 5, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 0.5261033610420723, + "size": [7, 7] + }, + "927660773022/MT3:MT4": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.41685204648787993, + "visionSimilarity": 0.42227588340693745, + "size": [7, 7] + }, + "927660773022/MT4.2.0:MT4": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 4, 9, 0, 8, 10, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 5, 7, 5, 1, 3, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 0, 7, 10, 7, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 0.46165417054774915, + "size": [7, 7] + }, + "927660773022/MT4.1.2:MT4": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 3, 1, 10, 1], + [1, 7, 1, 1, 1, 8, 1], + [1, 10, 6, 5, 6, 0, 1], + [1, 7, 1, 7, 1, 9, 1], + [1, 0, 8, 5, 1, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 0.542604828733473, + "size": [7, 7] + }, + "927660773022/MT3:MT6": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.35056984842978006, + "visionSimilarity": 0.4044799040406804, + "size": [7, 7] + }, + "927660773022/MT6.0.1:MT6": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 10, 1, 4, 6, 5, 1], + [1, 5, 1, 9, 1, 6, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 2, 1, 10, 1, 5, 1], + [1, 9, 4, 1, 3, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 0.7048329702586849, + "size": [7, 7] + }, + "927660773022/MT6.3.1:MT3": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 8, 5, 9, 1], + [1, 6, 1, 0, 1, 3, 1], + [1, 4, 9, 6, 10, 1, 1], + [1, 1, 1, 0, 1, 4, 1], + [1, 10, 5, 8, 2, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.35056984842978006, + "visionSimilarity": 0.39175541966003685, + "size": [7, 7] + }, + "927660773022/MT6:MT7": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.3367213779531951, + "visionSimilarity": 0.35905678352071746, + "size": [7, 7] + }, + "927660773022/MT7.2.2:MT6": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.3367213779531951, + "visionSimilarity": 0.35905678352071746, + "size": [7, 7] + }, + "927660773022/MT7.0.2:MT6": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 6, 4, 1, 10, 1], + [1, 6, 1, 9, 1, 5, 1], + [1, 8, 0, 6, 0, 8, 1], + [1, 5, 1, 10, 1, 2, 1], + [1, 9, 3, 1, 4, 9, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.3367213779531951, + "visionSimilarity": 0.3500092726622086, + "size": [7, 7] + }, + "927660773022/MT1:MT3": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 3, 1, 10, 7, 3, 1], + [1, 6, 1, 5, 1, 7, 1], + [1, 9, 1, 8, 1, 6, 1], + [1, 5, 8, 0, 1, 7, 1], + [1, 2, 1, 5, 7, 10, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.2167677774248223, + "visionSimilarity": 0.39706664726456226, + "size": [7, 7] + }, + "927660773022/MT1.1.0:MT1": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 8, 3, 8, 5, 1], + [1, 1, 0, 1, 6, 1, 1], + [1, 10, 2, 6, 2, 10, 1], + [1, 1, 0, 1, 6, 1, 1], + [1, 5, 8, 4, 8, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 1, + "visionSimilarity": 0.4414504734043763, + "size": [7, 7] + }, + "927660773022/MT5:MT7": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 9, 2, 9, 5, 1], + [1, 8, 0, 10, 0, 8, 1], + [1, 5, 9, 6, 9, 5, 1], + [1, 0, 8, 10, 8, 0, 1], + [1, 6, 0, 5, 0, 6, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.19841248564076336, + "visionSimilarity": 0.31025201437153405, + "size": [7, 7] + }, + "927660773022/MT7.3.0:MT5": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 6, 0, 5, 8, 5, 1], + [1, 0, 8, 9, 0, 9, 1], + [1, 5, 10, 6, 10, 2, 1], + [1, 0, 8, 9, 0, 9, 1], + [1, 6, 0, 5, 8, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 8, 5, 7, 0, 10, 1], + [1, 5, 6, 4, 6, 0, 1], + [1, 7, 3, 6, 3, 7, 1], + [1, 2, 6, 4, 6, 5, 1], + [1, 10, 2, 7, 5, 8, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.19841248564076336, + "visionSimilarity": 0.31004998180348137, + "size": [7, 7] + }, + "927660773022/MT1:MT4": { + "map1": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 8, 0, 2, 0, 8, 1], + [1, 4, 1, 6, 1, 3, 1], + [1, 8, 6, 2, 6, 8, 1], + [1, 5, 1, 10, 1, 5, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "map2": [ + [1, 1, 1, 1, 1, 1, 1], + [1, 5, 7, 10, 7, 0, 1], + [1, 8, 1, 6, 1, 8, 1], + [1, 3, 1, 5, 7, 5, 1], + [1, 1, 1, 6, 1, 1, 1], + [1, 10, 8, 0, 9, 4, 1], + [1, 1, 1, 1, 1, 1, 1] + ], + "topoSimilarity": 0.22249032228963764, + "visionSimilarity": 0.4274640099562086, + "size": [7, 7] + } + } +} diff --git a/minamo-eval.json b/minamo-eval.json new file mode 100644 index 0000000..210af0d --- /dev/null +++ b/minamo-eval.json @@ -0,0 +1 @@ +{"datasetId":122372575999,"data":{"493720395823/MT1:MT3":{"map1":[[1,1,1,1,1,1,1],[1,5,1,10,1,5,1],[1,4,7,5,7,0,1],[1,7,1,5,1,7,1],[1,5,1,7,1,5,1],[1,4,6,10,6,3,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,10,1,2,3,5,1],[1,0,1,1,1,9,1],[1,7,6,5,8,5,1],[1,0,1,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,1,1,1]],"topoSimilarity":0.2065427393585403,"visionSimilarity":0.4718364991384641,"size":[7,7]},"493720395823/MT1.3.0:MT3":{"map1":[[1,1,1,1,1,1,1],[1,4,5,7,4,5,1],[1,6,1,1,7,1,1],[1,10,7,5,5,10,1],[1,6,1,1,7,1,1],[1,3,5,7,0,5,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,10,1,2,3,5,1],[1,0,1,1,1,9,1],[1,7,6,5,8,5,1],[1,0,1,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,1,1,1]],"topoSimilarity":0.2065427393585403,"visionSimilarity":0.41306488335232266,"size":[7,7]},"493720395823/MT1:MT1.S0":{"map1":[[1,1,1,1,1,1,1],[1,5,1,10,1,5,1],[1,4,7,5,7,0,1],[1,7,1,5,1,7,1],[1,5,1,7,1,5,1],[1,4,6,10,6,3,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[10,5,1,10,1,5,1],[1,4,7,0,7,0,7],[2,7,10,5,1,7,1],[1,5,11,10,1,5,1],[1,4,6,11,2,3,1],[1,1,1,1,1,10,1]],"size":[7,7],"topoSimilarity":0.5449081303742903,"visionSimilarity":0.7458078360074276},"493720395823/MT1:MT1.S1":{"map1":[[1,1,1,1,1,1,1],[1,5,1,10,1,5,1],[1,4,7,5,7,0,1],[1,7,1,5,1,7,1],[1,5,1,7,1,5,1],[1,4,6,10,6,3,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,5,1,10,1,5,1],[1,4,7,5,7,0,1],[1,7,1,5,1,7,1],[2,5,1,7,1,3,1],[1,4,6,10,6,5,1],[1,1,1,1,1,1,1]],"size":[7,7],"topoSimilarity":0.567247802907439,"visionSimilarity":0.8901016472479844},"493720395823/MT2:MT3":{"map1":[[1,1,1,1,1,1,1],[1,7,0,10,8,5,1],[1,5,1,6,3,8,1],[1,7,5,1,6,2,1],[1,4,1,3,8,7,1],[1,10,5,6,0,5,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,10,1,2,3,5,1],[1,0,1,1,1,9,1],[1,7,6,5,8,5,1],[1,0,1,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,1,1,1]],"topoSimilarity":0.22348489590526102,"visionSimilarity":0.39789638665190225,"size":[7,7]},"493720395823/MT3:MT3":{"map1":[[1,1,1,1,1,1,1],[1,10,1,2,3,5,1],[1,0,1,1,1,9,1],[1,7,6,5,8,5,1],[1,0,1,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,10,1,2,3,5,1],[1,0,1,1,1,9,1],[1,7,6,5,8,5,1],[1,0,1,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,1,1,1]],"topoSimilarity":1,"visionSimilarity":1,"size":[7,7]},"493720395823/MT2:MT2.S0":{"map1":[[1,1,1,1,1,1,1],[1,7,0,10,8,5,1],[1,5,1,6,3,8,1],[1,7,5,1,6,2,1],[1,4,1,3,8,7,1],[1,10,5,6,0,5,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,7,0,10,8,5,1],[1,5,1,6,3,8,1],[1,7,5,1,6,2,1],[1,6,1,3,8,7,1],[1,10,5,6,0,5,1],[1,1,1,1,1,1,1]],"size":[7,7],"topoSimilarity":0.5752305500247755,"visionSimilarity":0.9629611812238851},"493720395823/MT2:MT2.S1":{"map1":[[1,1,1,1,1,1,1],[1,7,0,10,8,5,1],[1,5,1,6,3,8,1],[1,7,5,1,6,2,1],[1,4,1,3,8,7,1],[1,10,5,6,0,5,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,7,0,10,8,5,1],[1,5,1,6,3,8,1],[1,7,5,1,6,2,1],[1,4,1,3,8,7,1],[1,10,5,6,0,5,1],[1,1,1,1,1,1,1]],"size":[7,7],"topoSimilarity":0.4348511401304243,"visionSimilarity":1},"493720395823/MT2:MT2.S2":{"map1":[[1,1,1,1,1,1,1],[1,7,0,10,8,5,1],[1,5,1,6,3,8,1],[1,7,5,1,6,2,1],[1,4,1,3,8,7,1],[1,10,5,6,0,5,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,7,0,10,8,0,7],[1,5,1,6,3,5,1],[1,7,1,5,6,2,1],[1,1,4,3,8,7,1],[1,10,2,6,0,9,1],[1,1,1,1,1,1,1]],"size":[7,7],"topoSimilarity":0.8801898418624259,"visionSimilarity":0.7236293949262109},"493720395823/MT2:MT2.S3":{"map1":[[1,1,1,1,1,1,1],[1,7,0,10,8,5,1],[1,5,1,6,3,8,1],[1,7,5,1,6,2,1],[1,4,1,3,8,7,1],[1,10,5,6,0,5,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,9,1,1,1],[1,7,0,8,8,5,1],[1,5,1,2,3,8,1],[1,7,5,1,6,2,1],[1,4,1,3,8,7,1],[1,10,5,6,0,5,1],[1,1,1,1,5,1,1]],"size":[7,7],"topoSimilarity":0.45583165677638066,"visionSimilarity":0.8856848310017071},"493720395823/MT3:MT4":{"map1":[[1,1,1,1,1,1,1],[1,10,1,2,3,5,1],[1,0,1,1,1,9,1],[1,7,6,5,8,5,1],[1,0,1,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,10,1,3,1,10,1],[1,5,1,9,1,5,1],[1,7,1,5,1,7,1],[1,0,1,7,1,0,1],[1,5,7,4,7,5,1],[1,1,1,1,1,1,1]],"topoSimilarity":0.48653134914464213,"visionSimilarity":0.48784804748656435,"size":[7,7]},"493720395823/MT4:MT4":{"map1":[[1,1,1,1,1,1,1],[1,10,1,3,1,10,1],[1,5,1,9,1,5,1],[1,7,1,5,1,7,1],[1,0,1,7,1,0,1],[1,5,7,4,7,5,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,10,1,3,1,10,1],[1,5,1,9,1,5,1],[1,7,1,5,1,7,1],[1,0,1,7,1,0,1],[1,5,7,4,7,5,1],[1,1,1,1,1,1,1]],"topoSimilarity":1,"visionSimilarity":1,"size":[7,7]},"493720395823/MT4.3.1:MT3":{"map1":[[1,1,1,1,1,1,1],[1,10,5,7,0,5,1],[1,1,1,1,1,7,1],[1,3,9,5,7,4,1],[1,1,1,1,1,7,1],[1,10,5,7,0,5,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,10,1,2,3,5,1],[1,0,1,1,1,9,1],[1,7,6,5,8,5,1],[1,0,1,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,1,1,1]],"topoSimilarity":0.48653134914464213,"visionSimilarity":0.5025169154420001,"size":[7,7]},"493720395823/MT3:MT3.S0":{"map1":[[1,1,1,1,1,1,1],[1,10,1,2,3,5,1],[1,0,1,1,1,9,1],[1,7,6,5,8,5,1],[1,0,1,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,2,1,1,1],[10,10,1,1,3,5,0],[1,0,7,1,1,9,1],[1,8,5,6,8,5,9],[1,0,7,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,6,10,1]],"size":[7,7],"topoSimilarity":0.44351065052305955,"visionSimilarity":0.6994945748274667},"493720395823/MT3:MT3.S1":{"map1":[[1,1,1,1,1,1,1],[1,10,1,2,3,5,1],[1,0,1,1,1,9,1],[1,7,6,5,8,5,1],[1,0,1,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,1,1,1]],"map2":[[1,1,4,6,1,9,1],[10,10,1,2,3,5,0],[4,0,1,2,1,9,4],[1,5,6,9,8,5,2],[1,0,1,9,1,9,1],[1,10,1,2,11,3,1],[1,1,1,6,1,1,1]],"size":[7,7],"topoSimilarity":0.21729134636109018,"visionSimilarity":0.6808238833988753},"493720395823/MT3:MT3.S2":{"map1":[[1,1,1,1,1,1,1],[1,10,1,2,3,5,1],[1,0,1,1,1,9,1],[1,7,6,5,8,5,1],[1,0,1,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,1,1,1]],"map2":[[1,1,0,1,1,1,1],[6,10,1,6,3,5,1],[1,8,1,1,4,9,1],[11,7,6,5,6,5,1],[1,0,8,9,1,0,10],[1,10,1,2,1,3,1],[1,11,1,1,1,1,1]],"size":[7,7],"topoSimilarity":0.3285733032395127,"visionSimilarity":0.7083977459747435},"493720395823/MT3:MT3.S3":{"map1":[[1,1,1,1,1,1,1],[1,10,1,2,3,5,1],[1,0,1,1,1,9,1],[1,7,6,5,8,5,1],[1,0,1,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,10,1,2,3,5,1],[1,0,1,1,1,9,1],[1,7,6,5,8,5,1],[1,6,1,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,1,1,1]],"size":[7,7],"topoSimilarity":0.9072621687714714,"visionSimilarity":0.9784539875618482},"493720395823/MT3:MT3.S4":{"map1":[[1,1,1,1,1,1,1],[1,10,1,2,3,5,1],[1,0,1,1,1,9,1],[1,7,6,5,8,5,1],[1,0,1,9,1,9,1],[1,10,1,2,1,3,1],[1,1,1,1,1,1,1]],"map2":[[1,7,0,1,1,1,1],[1,9,2,2,1,5,1],[6,5,1,1,3,9,1],[1,10,6,5,8,5,1],[0,1,2,8,1,6,1],[1,10,1,2,1,3,2],[1,1,1,1,1,1,1]],"size":[7,7],"topoSimilarity":0.3414165670159324,"visionSimilarity":0.6162766382447302},"493720395823/MT1:MT2":{"map1":[[1,1,1,1,1,1,1],[1,5,1,10,1,5,1],[1,4,7,5,7,0,1],[1,7,1,5,1,7,1],[1,5,1,7,1,5,1],[1,4,6,10,6,3,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,7,0,10,8,5,1],[1,5,1,6,3,8,1],[1,7,5,1,6,2,1],[1,4,1,3,8,7,1],[1,10,5,6,0,5,1],[1,1,1,1,1,1,1]],"topoSimilarity":0.5008635446660876,"visionSimilarity":0.3837740847123484,"size":[7,7]},"493720395823/MT1:MT1":{"map1":[[1,1,1,1,1,1,1],[1,5,1,10,1,5,1],[1,4,7,5,7,0,1],[1,7,1,5,1,7,1],[1,5,1,7,1,5,1],[1,4,6,10,6,3,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,5,1,10,1,5,1],[1,4,7,5,7,0,1],[1,7,1,5,1,7,1],[1,5,1,7,1,5,1],[1,4,6,10,6,3,1],[1,1,1,1,1,1,1]],"topoSimilarity":1,"visionSimilarity":1,"size":[7,7]},"493720395823/MT2:MT2":{"map1":[[1,1,1,1,1,1,1],[1,7,0,10,8,5,1],[1,5,1,6,3,8,1],[1,7,5,1,6,2,1],[1,4,1,3,8,7,1],[1,10,5,6,0,5,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,7,0,10,8,5,1],[1,5,1,6,3,8,1],[1,7,5,1,6,2,1],[1,4,1,3,8,7,1],[1,10,5,6,0,5,1],[1,1,1,1,1,1,1]],"topoSimilarity":1,"visionSimilarity":1,"size":[7,7]},"493720395823/MT1.3.2:MT1":{"map1":[[1,1,1,1,1,1,1],[1,3,5,7,0,5,1],[1,6,1,1,7,1,1],[1,10,7,5,5,10,1],[1,6,1,1,7,1,1],[1,4,5,7,4,5,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,5,1,10,1,5,1],[1,4,7,5,7,0,1],[1,7,1,5,1,7,1],[1,5,1,7,1,5,1],[1,4,6,10,6,3,1],[1,1,1,1,1,1,1]],"topoSimilarity":1,"visionSimilarity":0.441092858445748,"size":[7,7]},"493720395823/MT1:MT1.S2":{"map1":[[1,1,1,1,1,1,1],[1,5,1,10,1,5,1],[1,4,7,5,7,0,1],[1,7,1,5,1,7,1],[1,5,1,7,1,5,1],[1,4,6,10,6,3,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,5,1,10,1,11,1],[1,4,2,5,7,0,1],[1,9,1,5,1,7,1],[1,5,6,7,1,5,1],[1,4,1,10,6,3,1],[1,1,1,1,1,1,1]],"size":[7,7],"topoSimilarity":0.4219726518104551,"visionSimilarity":0.8583034384133457},"493720395823/MT1:MT1.S3":{"map1":[[1,1,1,1,1,1,1],[1,5,1,10,1,5,1],[1,4,7,5,7,0,1],[1,7,1,5,1,7,1],[1,5,1,7,1,5,1],[1,4,6,10,6,3,1],[1,1,1,1,1,1,1]],"map2":[[0,1,5,1,1,1,1],[1,5,1,10,1,5,1],[1,4,7,2,7,0,1],[4,7,1,5,2,7,5],[1,5,3,7,1,5,1],[1,4,6,10,6,3,2],[1,1,1,5,1,7,1]],"size":[7,7],"topoSimilarity":0.4817350229961716,"visionSimilarity":0.735203094786626},"493720395823/MT2:MT4":{"map1":[[1,1,1,1,1,1,1],[1,7,0,10,8,5,1],[1,5,1,6,3,8,1],[1,7,5,1,6,2,1],[1,4,1,3,8,7,1],[1,10,5,6,0,5,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,10,1,3,1,10,1],[1,5,1,9,1,5,1],[1,7,1,5,1,7,1],[1,0,1,7,1,0,1],[1,5,7,4,7,5,1],[1,1,1,1,1,1,1]],"topoSimilarity":0.40433747080046273,"visionSimilarity":0.4039271469696363,"size":[7,7]},"493720395823/MT2:MT2.S4":{"map1":[[1,1,1,1,1,1,1],[1,7,0,10,8,5,1],[1,5,1,6,3,8,1],[1,7,5,1,6,2,1],[1,4,1,3,8,7,1],[1,10,5,6,0,5,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,7,0,10,8,5,1],[1,5,1,6,3,8,1],[1,7,5,1,6,2,1],[1,4,1,3,8,7,1],[1,1,5,6,0,5,1],[1,1,1,1,1,1,1]],"size":[7,7],"topoSimilarity":0.8458854767412373,"visionSimilarity":0.980041160177175},"493720395823/MT1:MT4":{"map1":[[1,1,1,1,1,1,1],[1,5,1,10,1,5,1],[1,4,7,5,7,0,1],[1,7,1,5,1,7,1],[1,5,1,7,1,5,1],[1,4,6,10,6,3,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,10,1,3,1,10,1],[1,5,1,9,1,5,1],[1,7,1,5,1,7,1],[1,0,1,7,1,0,1],[1,5,7,4,7,5,1],[1,1,1,1,1,1,1]],"topoSimilarity":0.41287017795671055,"visionSimilarity":0.539164656948717,"size":[7,7]},"493720395823/MT4.2.3:MT4":{"map1":[[1,1,1,1,1,1,1],[1,10,1,3,1,10,1],[1,5,1,9,1,5,1],[1,7,1,5,1,7,1],[1,0,1,7,1,0,1],[1,5,7,4,7,5,1],[1,1,1,1,1,1,1]],"map2":[[1,1,1,1,1,1,1],[1,10,1,3,1,10,1],[1,5,1,9,1,5,1],[1,7,1,5,1,7,1],[1,0,1,7,1,0,1],[1,5,7,4,7,5,1],[1,1,1,1,1,1,1]],"topoSimilarity":1,"visionSimilarity":1,"size":[7,7]}}} \ No newline at end of file diff --git a/minamo/dataset.py b/minamo/dataset.py new file mode 100644 index 0000000..31b89b8 --- /dev/null +++ b/minamo/dataset.py @@ -0,0 +1,29 @@ +import json +import torch +from torch.utils.data import Dataset + +def load_data(path: str): + with open(path, 'r', encoding="utf-8") as f: + data = json.load(f) + + data_list = [] + for value in data["data"].values(): + data_list.append(value) + + return data_list + +class MinamoDataset(Dataset): + def __init__(self, data_path: str): + self.data = load_data(data_path) # 自定义数据加载函数 + + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + item = self.data[idx] + return ( + torch.LongTensor(item['map1']), + torch.LongTensor(item['map2']), + torch.FloatTensor([item['visionSimilarity']]), + torch.FloatTensor([item['topoSimilarity']]) + ) diff --git a/minamo/model/loss.py b/minamo/model/loss.py new file mode 100644 index 0000000..94a2cdf --- /dev/null +++ b/minamo/model/loss.py @@ -0,0 +1,14 @@ +import torch.nn as nn + +class MinamoLoss(nn.Module): + def __init__(self, vision_weight=0.4, topo_weight=0.6): + super().__init__() + self.vision_weight = vision_weight + self.topo_weight = topo_weight + self.mse = nn.MSELoss() + + def forward(self, vis_pred, topo_pred, vis_true, topo_true): + # print(vis_pred[0].item(), topo_pred[0].item(), vis_true[0].item(), topo_true[0].item()) + vis_loss = self.mse(vis_pred, vis_true) + topo_loss = self.mse(topo_pred, topo_true) + return self.vision_weight * vis_loss + self.topo_weight * topo_loss \ No newline at end of file diff --git a/minamo/model/model.py b/minamo/model/model.py new file mode 100644 index 0000000..5ebe553 --- /dev/null +++ b/minamo/model/model.py @@ -0,0 +1,121 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +class DualAttention(nn.Module): + def __init__(self, in_channels): + super().__init__() + # 空间注意力 + self.spatial = nn.Sequential( + nn.Conv2d(in_channels, 1, 1), + nn.Sigmoid() + ) + # 通道注意力 + self.channel = nn.Sequential( + nn.AdaptiveAvgPool2d(1), + nn.Conv2d(in_channels, in_channels//8, 1), + nn.ReLU(), + nn.Conv2d(in_channels//8, in_channels, 1), + nn.Sigmoid() + ) + + def forward(self, x): + return x * self.spatial(x) + x * self.channel(x) + +class DirectionalAttention(nn.Module): + def __init__(self, kernel_size=7): + super().__init__() + self.direction_convs = nn.ModuleDict({ + dir: nn.Conv2d(1, 1, kernel_size, padding=kernel_size//2, + padding_mode='replicate') + for dir in ['h', 'v', 'd1', 'd2'] + }) + + def forward(self, x): + B, C, H, W = x.shape + # 各方向特征 + h_att = self.direction_convs['h'](x.mean(1, keepdim=True)) + v_att = self.direction_convs['v'](x.mean(1, keepdim=True)) + d1_att = self.direction_convs['d1'](x.mean(1, keepdim=True)) + d2_att = self.direction_convs['d2'](x.mean(1, keepdim=True)) + + # 动态融合 + combined = torch.stack([h_att, v_att, d1_att, d2_att], dim=1) # [B,4,1,H,W] + att_weights = F.softmax(combined.mean([3,4]), dim=1) # [B,4] + return x * (combined * att_weights.unsqueeze(-1).unsqueeze(-1)).sum(1) + +class MinamoModel(nn.Module): + def __init__(self, num_tile_types, embedding_dim=64, conv_channels=256): + super().__init__() + # 嵌入层处理不同图块类型 + self.embedding = nn.Embedding(num_tile_types, embedding_dim) + + # 共享特征提取的卷积层 + self.conv_layers = nn.Sequential( + nn.Conv2d(embedding_dim, conv_channels, 3, padding=1), + DualAttention(conv_channels), + DirectionalAttention(), + nn.ReLU(), + nn.BatchNorm2d(conv_channels), + + nn.Conv2d(conv_channels, conv_channels*2, 3, padding=1), + DualAttention(conv_channels*2), + DirectionalAttention(), + nn.ReLU(), + nn.BatchNorm2d(conv_channels*2), + + nn.Conv2d(conv_channels*2, conv_channels*4, 3, padding=1), + DualAttention(conv_channels*4), + DirectionalAttention(), + nn.ReLU(), + nn.BatchNorm2d(conv_channels*4), + ) + + # 自适应池化处理任意尺寸 + self.pool = nn.ModuleDict({ + 'avg': nn.AdaptiveAvgPool2d((1,1)), + 'max': nn.AdaptiveMaxPool2d((1,1)) + }) + + # 多任务预测头 + head_dim = conv_channels * 4 * 2 * 4 # 2个池化,四个交互项 + self.vision_head = nn.Sequential( + nn.Linear(head_dim, 512), + nn.ReLU(), + nn.Dropout(0.3), + nn.Linear(512, 1), + nn.Sigmoid() + ) + + self.topo_head = nn.Sequential( + nn.Linear(head_dim, 512), + nn.GELU(), + nn.Dropout(0.3), + nn.Linear(512, 256), + nn.GELU(), + nn.Linear(256, 1), + nn.Sigmoid() + ) + + + def forward(self, map1, map2): + # 增强特征提取 + def process_map(x): + x = self.embedding(x).permute(0,3,1,2) + x = self.conv_layers(x) + return torch.cat([ + self.pool['avg'](x), + self.pool['max'](x) + ], dim=1).flatten(1) + + f1 = process_map(map1) + f2 = process_map(map2) + + # 特征融合 + combined = torch.cat([f1, f2, f1-f2, f1*f2], dim=1) # [B, 256] + + # 多任务输出 + vision_sim = self.vision_head(combined) + topo_sim = self.topo_head(combined) + + return vision_sim, topo_sim diff --git a/minamo/train.py b/minamo/train.py new file mode 100644 index 0000000..179f424 --- /dev/null +++ b/minamo/train.py @@ -0,0 +1,131 @@ +import os +from datetime import datetime +import torch +import torch.optim as optim +from torch.utils.data import DataLoader +from tqdm import tqdm +from .model.model import MinamoModel +from .model.loss import MinamoLoss +from .dataset import MinamoDataset + +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +os.makedirs("result", exist_ok=True) + +epochs = 100 + +def collate_fn(batch): + """动态处理不同尺寸地图的批处理""" + map1_batch = [item[0] for item in batch] + map2_batch = [item[1] for item in batch] + vis_sim = torch.cat([item[2] for item in batch]) + topo_sim = torch.cat([item[3] for item in batch]) + + # 保持批次内地图尺寸一致(根据问题描述) + assert all(m.shape == map1_batch[0].shape for m in map1_batch), \ + "对比地图必须尺寸相同" + + return ( + torch.stack(map1_batch), # (B, H, W) + torch.stack(map2_batch), # (B, H, W) + vis_sim, + topo_sim + ) + +def train(): + print(f"Using {"cuda" if torch.cuda.is_available() else "cpu"} to train model.") + model = MinamoModel(32) + model.to(device) + + # 准备数据集 + dataset = MinamoDataset("F:/github-ai/ginka-generator/minamo-dataset.json") + val_dataset = MinamoDataset("F:/github-ai/ginka-generator/minamo-eval.json") + dataloader = DataLoader( + dataset, + batch_size=32, + shuffle=True + ) + val_loader = DataLoader( + val_dataset, + batch_size=32, + shuffle=True + ) + + # 设定优化器与调度器 + optimizer = optim.AdamW(model.parameters(), lr=3e-5, weight_decay=1e-4) + scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs) + criterion = MinamoLoss() + + # 开始训练 + for epoch in tqdm(range(epochs)): + model.train() + total_loss = 0 + + for batch in dataloader: + # 数据迁移到设备 + map1, map2, vision_simi, topo_simi = batch + map1 = map1.to(device) # 转为 [B, C, H, W] + map2 = map2.to(device) + topo_simi = topo_simi.to(device) + vision_simi = vision_simi.to(device) + + # print(map1.shape, map2.shape) + + # 前向传播 + optimizer.zero_grad() + vision_pred, topo_pred = model(map1, map2) + + # 计算损失 + loss = criterion(vision_pred, topo_pred, vision_simi, topo_simi) + + # 反向传播 + loss.backward() + optimizer.step() + total_loss += loss.item() + + total_norm = 0 + for p in model.parameters(): + if p.grad is not None: + param_norm = p.grad.detach().data.norm(2) + total_norm += param_norm.item() ** 2 + total_norm = total_norm ** 0.5 + # tqdm.write(f"Gradient Norm: {total_norm:.4f}") # 正常应保持在1~100之间 + + ave_loss = total_loss / len(dataloader) + tqdm.write(f"[INFO {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Epoch: {epoch} | loss: {ave_loss:.6f} | lr: {(optimizer.param_groups[0]['lr']):.6f}") + + # 学习率调整 + scheduler.step() + + # 每十轮推理一次验证集 + if (epoch + 1) % 10 == 0: + model.eval() + val_loss = 0 + with torch.no_grad(): + for val_batch in val_loader: + map1_val, map2_val, vision_simi_val, topo_simi_val = val_batch + map1_val = map1_val.to(device) + map2_val = map2_val.to(device) + vision_simi_val = vision_simi_val.to(device) + topo_simi_val = topo_simi_val.to(device) + + vision_pred_val, topo_pred_val = model(map1_val, map2_val) + loss_val = criterion( + vision_pred_val, topo_pred_val, + vision_simi_val, topo_simi_val + ) + val_loss += loss_val.item() + + avg_val_loss = val_loss / len(val_loader) + tqdm.write(f"[INFO {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Validation :: loss: {avg_val_loss:.6f}") + + + print("Train ended.") + + torch.save({ + "model_state": model.state_dict(), + "optimizer_state": optimizer.state_dict(), + }, "result/minamo.pth") + +if __name__ == "__main__": + torch.set_num_threads(2) + train() diff --git a/shared/attention.py b/shared/attention.py new file mode 100644 index 0000000..e485ee1 --- /dev/null +++ b/shared/attention.py @@ -0,0 +1,56 @@ +import torch +import torch.nn as nn + +class ChannelAttention(nn.Module): + """通道注意力模块""" + def __init__(self, channels, reduction=8): + super().__init__() + # 通道注意力 + self.channel_att = nn.Sequential( + nn.AdaptiveAvgPool2d(1), + nn.Conv2d(channels, channels//reduction, 1), + nn.ReLU(), + nn.Conv2d(channels//reduction, channels, 1), + nn.Sigmoid() + ) + + def forward(self, x): + # 通道注意力 + c_att = self.channel_att(x) + x = x * c_att + return x + +class SpatialAttention(nn.Module): + """空间注意力模块""" + def __init__(self): + super().__init__() + # 空间注意力 + self.spatial_att = nn.Sequential( + nn.Conv2d(2, 1, 7, padding=3), + nn.Sigmoid() + ) + + def forward(self, x): + # 空间注意力 + max_pool = torch.max(x, dim=1, keepdim=True)[0] + avg_pool = torch.mean(x, dim=1, keepdim=True) + s_att = self.spatial_att(torch.cat([max_pool, avg_pool], dim=1)) + return x * s_att + +class CBAM(nn.Module): + """通道与空间注意力结合""" + def __init__(self, channels, reduction=8): + super().__init__() + # 通道注意力 + self.channel_att = ChannelAttention(channels, reduction) + # 空间注意力 + self.spatial_att = SpatialAttention() + + def forward(self, x): + # 通道注意力 + c_att = self.channel_att(x) + x = x * c_att + + # 空间注意力 + s_att = self.spatial_att(x) + return x * s_att