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