diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 00000000..b96efb79 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. +config: ec667ad656319d7e1c76aa3eee6f64e0 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/.nojekyll b/.doctrees/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/.doctrees/README.doctree b/.doctrees/README.doctree new file mode 100644 index 00000000..9311e9cb Binary files /dev/null and b/.doctrees/README.doctree differ diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle new file mode 100644 index 00000000..95cdd544 Binary files /dev/null and b/.doctrees/environment.pickle differ diff --git a/.doctrees/index.doctree b/.doctrees/index.doctree new file mode 100644 index 00000000..bf81cf3c Binary files /dev/null and b/.doctrees/index.doctree differ diff --git a/.doctrees/modules.doctree b/.doctrees/modules.doctree new file mode 100644 index 00000000..66da6f6e Binary files /dev/null and b/.doctrees/modules.doctree differ diff --git a/.doctrees/moptipyapps.binpacking2d.doctree b/.doctrees/moptipyapps.binpacking2d.doctree new file mode 100644 index 00000000..8a4dcfb5 Binary files /dev/null and b/.doctrees/moptipyapps.binpacking2d.doctree differ diff --git a/.doctrees/moptipyapps.binpacking2d.encodings.doctree b/.doctrees/moptipyapps.binpacking2d.encodings.doctree new file mode 100644 index 00000000..7fef2c49 Binary files /dev/null and b/.doctrees/moptipyapps.binpacking2d.encodings.doctree differ diff --git a/.doctrees/moptipyapps.binpacking2d.instgen.doctree b/.doctrees/moptipyapps.binpacking2d.instgen.doctree new file mode 100644 index 00000000..f28d2152 Binary files /dev/null and b/.doctrees/moptipyapps.binpacking2d.instgen.doctree differ diff --git a/.doctrees/moptipyapps.binpacking2d.objectives.doctree b/.doctrees/moptipyapps.binpacking2d.objectives.doctree new file mode 100644 index 00000000..49ac42f1 Binary files /dev/null and b/.doctrees/moptipyapps.binpacking2d.objectives.doctree differ diff --git a/.doctrees/moptipyapps.doctree b/.doctrees/moptipyapps.doctree new file mode 100644 index 00000000..19df8477 Binary files /dev/null and b/.doctrees/moptipyapps.doctree differ diff --git a/.doctrees/moptipyapps.dynamic_control.controllers.doctree b/.doctrees/moptipyapps.dynamic_control.controllers.doctree new file mode 100644 index 00000000..1f7e319a Binary files /dev/null and b/.doctrees/moptipyapps.dynamic_control.controllers.doctree differ diff --git a/.doctrees/moptipyapps.dynamic_control.doctree b/.doctrees/moptipyapps.dynamic_control.doctree new file mode 100644 index 00000000..68765a6a Binary files /dev/null and b/.doctrees/moptipyapps.dynamic_control.doctree differ diff --git a/.doctrees/moptipyapps.dynamic_control.systems.doctree b/.doctrees/moptipyapps.dynamic_control.systems.doctree new file mode 100644 index 00000000..25d63861 Binary files /dev/null and b/.doctrees/moptipyapps.dynamic_control.systems.doctree differ diff --git a/.doctrees/moptipyapps.order1d.doctree b/.doctrees/moptipyapps.order1d.doctree new file mode 100644 index 00000000..2f3d1599 Binary files /dev/null and b/.doctrees/moptipyapps.order1d.doctree differ diff --git a/.doctrees/moptipyapps.qap.doctree b/.doctrees/moptipyapps.qap.doctree new file mode 100644 index 00000000..f355e417 Binary files /dev/null and b/.doctrees/moptipyapps.qap.doctree differ diff --git a/.doctrees/moptipyapps.qap.qaplib.doctree b/.doctrees/moptipyapps.qap.qaplib.doctree new file mode 100644 index 00000000..d23774c5 Binary files /dev/null and b/.doctrees/moptipyapps.qap.qaplib.doctree differ diff --git a/.doctrees/moptipyapps.tests.doctree b/.doctrees/moptipyapps.tests.doctree new file mode 100644 index 00000000..79a44020 Binary files /dev/null and b/.doctrees/moptipyapps.tests.doctree differ diff --git a/.doctrees/moptipyapps.tsp.doctree b/.doctrees/moptipyapps.tsp.doctree new file mode 100644 index 00000000..ca0b9fdc Binary files /dev/null and b/.doctrees/moptipyapps.tsp.doctree differ diff --git a/.doctrees/moptipyapps.tsp.tsplib.doctree b/.doctrees/moptipyapps.tsp.tsplib.doctree new file mode 100644 index 00000000..fc02c112 Binary files /dev/null and b/.doctrees/moptipyapps.tsp.tsplib.doctree differ diff --git a/.doctrees/moptipyapps.ttp.doctree b/.doctrees/moptipyapps.ttp.doctree new file mode 100644 index 00000000..76847927 Binary files /dev/null and b/.doctrees/moptipyapps.ttp.doctree differ diff --git a/.doctrees/moptipyapps.ttp.robinx.doctree b/.doctrees/moptipyapps.ttp.robinx.doctree new file mode 100644 index 00000000..14447a9e Binary files /dev/null and b/.doctrees/moptipyapps.ttp.robinx.doctree differ diff --git a/.nojekyll b/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/CONTRIBUTING_md.html b/CONTRIBUTING_md.html new file mode 100755 index 00000000..130b2ca7 --- /dev/null +++ b/CONTRIBUTING_md.html @@ -0,0 +1 @@ +Contributing to moptipy

Contributing to moptipy

Thank you for supporting the moptipyapps, the applications using the Metaheuristic Optimization in Python (moptipy) package. moptipy is a library for implementing, using, and experimenting with metaheuristic optimization algorithms. Our project is developed for scientific, educational, and industrial applications. In this package here, you can find programs and codes using moptipy.

1. Contributing

One of the pillars of our project is extensive documentation, the implementation of many style-guides, the use of unit tests, and the use of many static code analysis tools. Our make build requires the code to pass the checks of more than 20 tools. This may make it complicated to submit code contributions via git pull requests.

The preferred way to contribute to this project therefore is by opening issues.

If you nevertheless submit a git pull or otherwise code-based contribution, then it should ideally pass all these checks. In other words, the make build requires the code to pass the checks of more than 20 tools should succeed on your local system.

If that is not possible, you can still submit the pull request or otherwise code-based contribution. However, we will then need to invest more work to manually check to see how and whether it can be integrated into our code base. On the one hand, this may take quite some time. On the other hand, it may also mean that we eventually reject the request and manually integrate a very modified version of the code -- but we would of course give proper credit. Finally, it could also turn out that we simply cannot integrate the contribution at the current time. We will, however, definitely try our best.

We believe that, in the long run, having very clearly documented code that follows best practices and is thoroughly tested wherever possible will benefit the value of our project. The downside is that it takes a lot of resources, time, and nit-picking energy.

If your contribution concerns the security of moptipy, please consider our security policy.

2. License

moptipy is provided to the public as open source software under the GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007. Terms for other licenses, e.g., for specific industrial applications, can be negotiated with Dr. Thomas Weise (who can be reached via the contact information below). Dr. Thomas Weise holds the copyright of this package except for the JSSP instance data in file moptipy/examples/jssp/instances.txt.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in moptipy by you shall be under the above license, terms, and conditions, without any additional terms or conditions. Unless explicitly stating otherwise, contributors accept that their contributions will be licensed under the project terms. This also means that they grant Dr. Thomas Weise non-exclusive copyright of their contributions.

3. Contact

If you have any questions or suggestions, please contact Prof. Dr. Thomas Weise (汤卫思教授) of the Institute of Applied Optimization (应用优化研究所, IAO) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥学院) in Hefei, Anhui, China (中国安徽省合肥市) via email to tweise@hfuu.edu.cn with CC to tweise@ustc.edu.cn.

diff --git a/LICENSE.html b/LICENSE.html new file mode 100644 index 00000000..eaeb1aa5 --- /dev/null +++ b/LICENSE.html @@ -0,0 +1,676 @@ +

                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
+
diff --git a/README.html b/README.html new file mode 100644 index 00000000..d667260e --- /dev/null +++ b/README.html @@ -0,0 +1,12 @@ +1. Introduction — moptipyapps 0.8.62 documentation

build pypi version pypi downloads coverage report

1. Introduction

moptipy is a library with implementations of metaheuristic optimization methods in Python 3.12 that also offers an environment for replicable experiments (flyer). moptipyapps is a collection of applications and experiments based on moptipy.

2. Installation

In order to use this package and to, e.g., run the example codes, you need to first install it using pip or some other tool that can install packages from PyPi. You can install the newest version of this library from PyPi using pip by doing

pip install moptipyapps
+

This will install the latest official release of our package as well as all dependencies. If you want to install the latest source code version from GitHub (which may not yet be officially released), you can do

pip install git+https://github.com/thomasWeise/moptipyapps.git
+

If you want to install the latest source code version from GitHub (which may not yet be officially released) and you have set up a private/public key for GitHub, you can also do:

git clone ssh://git@github.com/thomasWeise/moptipyapps
+pip install moptipyapps
+

This may sometimes work better if you are having trouble reaching GitHub via https or http.

You can also clone the repository and then run a make build, which will automatically install all dependencies, run all the tests, and then install the package on your system, too. This will work only on Linux, though. It also installs the dependencies for building, which include, e.g., those for unit testing and static analysis. If this build completes successful, you can be sure that moptipyapps will work properly on your machine.

All dependencies for using and running moptipyapps are listed at here. The additional dependencies for a full make build, including unit tests, static analysis, and the generation of documentation are listed here.

3. Applications

Here we list the applications of moptipy. Step by step, we will add more and more interesting optimization problems. For each problem, we provide means to load the problem instances and a basic infrastructure to construct optimization algorithms for solving them.

3.1. Two-Dimensional Bin Packing Problem

In the package moptipyapps.binpacking2d, we provide tools for experimenting and playing around with the two-dimensional bin packing problem. Bin packing is a classical domain from Operations Research. The goal is to pack objects into containers, the so-called bins. We address two-dimensional rectangular bin packing. We provide the bin packing instances from 2DPackLib as resources together with this package as well as the four non-trivial “Almost Squares in Almost Squares” instances. Each such instances defines a set of n_different_items objects Oi with i from 1..n_different_objects. Each object Oi is a rectangle with a given width and height. The object occur is a given multiplicity repetitions(O_i), i.e., either only once or multiple times. The bins are rectangles with a given width and height too. The goal of tackling such an instance is to package all the objects into as few as possible bins. The objects therefore may be rotated by 90 degrees.

We address this problem by representing a packing as a signed permutation with repetitions of the numbers 1..n_different_objects, where the number i occurs repetitions(O_i) times. If an object is to be placed in a rotated way, this is denoted by using -i instead of i. Such permutations are processed from beginning to end, placing the objects into bins as they come according to some heuristic encoding. We provide two variants of the Improved Bottom Left encoding. The first one closes bins as soon as one object cannot be placed into them. The second one tries to put each object in the earliest possible bin. While the former one is faster, the latter one leads to better packings.

We can then apply a black-box metaheuristic to search in the space of these signed permutations with repetitions. The objective function would be some measure consistent with the goal of minimizing the number of bins used.

Examples:

Important work on this code has been contributed by Mr. Rui ZHAO (赵睿), zr1329142665@163.com, a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥学院) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授), who then refined the implementations.

3.2. The Traveling Salesperson Problem (TSP)

In the package moptipyapps.tsp, we provide tools to run experiments and play around with the Traveling Salesperson Problem (TSP) . A TSP instance is defined as a fully-connected graph with n_cities nodes. Each edge in the graph has a weight, which identifies the distance between the nodes. The goal is to find the shortest tour that visits every single node in the graph exactly once and then returns back to its starting node. Then nodes are usually called cities. A tour can be represented in path representation, which means that it is stored as a permutation of the numbers 0 to n_cities-1. The number at index k identifies that k-th city to visit. So the first number in the permutation identifies the first city, the second number the second city, and so on. The length of the tour can be computed by summing up the distances from the k-th city to the k+1-st city, for k in 0..n_cities-2 and then adding the distance from the last city to the first city.

We use the TSP instances from TSPLib, the maybe most important benchmark set for the TSP. 110 of these instances are included as resources in this package.

Examples:

Important work on this code has been contributed by Mr. Tianyu LIANG (梁天宇), liangty@stu.hfuu.edu.cn a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥学院) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

The Traveling Tournament Problem (TTP) is related to the TTP.

3.3. Dynamic Controller Synthesis

Another interesting example for optimization is the synthesis of active controllers for dynamic systems. Dynamic systems have a state that changes over time based on some laws. These laws may be expressed as ordinary differential equations, for example. The classical Stuart-Landau system, for instance, represents an object whose coordinates on a two-dimensional plane change as follows:

sigma = 0.1 - x² - y²
+dx/dt = sigma * x - y
+dy/dt = sigma * y + x
+

Regardless on which (x, y) the object initially starts, it tends to move to a circular rotation path centered around the origin with radius sqrt(0.1). Now we try to create a controller ctrl for such a system that moves the object from this periodic circular path into a fixed and stable location. The controller ctrl receives the current state, i.e., the object location, as input and can influence the system as follows:

sigma = 0.1 - x² - y²
+c = ctrl(x, y)
+dx/dt = sigma * x - y
+dy/dt = sigma * y + x + c
+

What we try to find is the controller which can bring move object to the origin (0, 0) as quickly as possible while expending the least amount of force, i.e., having the smallest aggregated c values over time.

3.4. The Traveling Tournament Problem (TTP)

In the package moptipyapps.ttp, we provide a set of classes and tools to explore the Traveling Tournament Problem (TTP). In a TTP, we have an even number of n teams. Each team plays a tournament against every other team. If the tournament is a single round-robin tournament, each team plays exactly once against every other team. In the more common double round-robin tournament, each team plays twice against every other team — once at home and once at the place of the other team. A tournament takes (n - 1) * rounds days in total, where rounds = 2 for double round-robin. Now additionally to the basic constraints dictated by logic (if team A plays at home against team B on day d, then team B has an “away” game against team A on that day d and so on), there are also additional constraints. For instance, no team should play a continuous streak of home (or away) games longer than m days, where m usually is m = 3. Also, if teams A and B play against each other, then there must be at least p games in between before they play each other again, usually with p = 1.

Now the first hurdle is to find a game plan that has n / 2 games on each day (since there are n teams and each plays against one other team) that satisfies the above constraints. The second problem is that this is not all: For each TTP, a distance matrix is defined, very much like for the TSP. The goal is to find a feasible game schedule where the overall travel distances are minimal.

Examples:

3.5. The Quadratic Assignment Problem (QAP)

In the package moptipyapps.qap, we implement some utilities to play with the Quadratic Assignment Problem (QAP). The QAP is one of the very classical problems from Operations Research. Imagine you are planning the layout for a factory. The goal is to assign n facilities (say, machines or workshops) to n locations. Now between the facilities, there exists a flow of goods. The output of one facility may be the input of another one and vice versa. The amount of stuff to be transported is likely to be different between different facilities. Between some facilities, a lot of things may need to be transport. Between others, there could be no exchange of material or only very little. The available locations also have different distances among each other. Some locations are closer, some are farther from each other. The goal is to find an assignment of facilities to locations such that the overall sum of the product of flow and distance for each facility pair gets minimized. To this end, solutions can be represented as permutations of facilities determining the order in which they are placed on the locations 1 to n.

Examples:

3.6. One-Dimensional Ordering

In the package moptipyapps.order1d, we implement what I would like to call the “one-dimensional ordering problem”. Imagine that you have n objects and you only know the distances between them. You want to arrange these objects on one axis, e.g., along the horizontal (x) axis, i.e., in a one-dimensional space. Now what you care about is to reflect the neighborhood structure among the objects (as defined by the distance matrix that you got) to the one-dimensional space. So the closest neighbor of a given object based on the distance matrix should also be the closest neighbor on the one-dimensional axis.

The goal of solving this problem is thus to arrange the n objects on a 1-dimensional (e.g., horizontal) axis given a distance matrix describing (maybe derived from their location in a potentially high-dimensional or unstructured space). The objects should be arranged in such a way that, for each object,

  • the nearest neighbors on the 1-dimensional axis are also the nearest neighbors in the original space (according to the distance matrix provided),

  • the second nearest neighbors on the 1-dimensional axis are also the second nearest neighbors in the original space (according to the distance matrix provided),

  • the third nearest neighbors on the 1-dimensional axis are also the third nearest neighbors in the original space (according to the distance matrix provided),

  • and so on; with (quadratically) decreasing weights of neighbor distance ranks.

The original distances be limited to integers for the sake of simplicity, but we may use floats as well if we want to. Either way, we do not care about the actual precise distances (e.g., something like “0.001”) between the objects on either the one-dimensional nor the original space. Only about the distance ranks, i.e., about “2nd nearest neighbor,” but not “0.012 distance units away.” The solutions of this problem are thus permutations (orders) of the objects. Of course, if we really want to plot the objects, such a permutation can easily be translated to x-coordinates, say, by dividing the index of an object by the number of objects, which nets values in [0,1]. But basically, we reduce the task to finding permutations of objects that reflect the neighbor structure of the original space as closely as possible.

If such a problem is solved correctly, then the arrangement on the one-dimensional axis should properly reflect the arrangement of the objects in the original space. Of course, solving this problem exactly may not actually be possible, since an object on a one-dimensional axis may either have exactly two i-nearest-neighbors (if it is at least i slots away from either end of the permutation) or exactly 1 such neighbor, if it is closer that i units. The object directly at the start of the permutation has only 1 nearest neighbor (the object that comes next). That next object, however, has two, namely the first object and the third object. In the original space where the objects come from, however, there may be any number of “nearest neighbors.” Imagine a two-dimensional space where one object sits at the center of a circle of other objects. Then all other objects are its nearest neighbors, whereas an object on the circle either has exactly two nearest neighbors or, maybe, in the odd situation that the radius equals a multiple of the distance to the neighbors on the circle, three. Such a structure cannot be represented exactly in one dimension.

But that’s OK. Because we mainly do this for visualization purposes anyway.

Examples:

4. Unit Tests and Static Analysis

When developing and applying randomized algorithms, proper testing and checking of the source code is of utmost importance. If we apply a randomized metaheuristic to an optimization problem, then we usually do not which solution quality we can achieve. Therefore, we can usually not know whether we have implemented the algorithm correctly. In other words, detecting bugs is very hard. Unfortunately, this holds also for the components of the algorithms, such as the search operators, especially if they are randomized as well. A bug may lead to worse results and we might not even notice that the worse result quality is caused by the bug. We may think that the algorithm is just not working well on the problem.

Therefore, we need to test all components of the algorithm as far as we can. We can try check, for example, if a randomized nullary search operator indeed creates different solutions when invoked several times. We can try to check whether an algorithm fails with an exception. We can try to check whether the search operators create valid solutions and whether the algorithm passes valid solutions to the objective function. We can try to whether an objective function produces finite objective values and if bounds are specified for the objective values, we can check whether they indeed fall within these bounds. Now we cannot prove that there are no such bugs, due to the randomization. But by testing a few hundred times, we can at least detect very obvious and pathological bugs.

To ease such testing for you, we provide a set of tools for testing implemented algorithms, spaces, and operators in the package moptipyapps.tests. Here, you can find functions where you pass in instances of your implemented components and they are checked for compliance with the moptipy API and the problem setups defined in moptipyapps. In other words, if you go and implement your own algorithms, operators, and optimization problems, you can use our pre-defined unit tests to give them a thorough check before using them in production. Again, such tests cannot prove the absence of bugs. But they can at least give you a fair shot to detect pathological errors before wasting serious experimentation time.

We also try to extensively test our own code, see the coverage report of moptipy and moptipyapps.

Another way to try to improve and maintain code quality is to use static code analysis and type hints where possible and reasonable. A static analysis tool can inform you about, e.g., unused variables, which often result from a coding error. It can tell you if the types of expressions do not match, which usually indicates a coding error, too. It can tell you if you perform some security-wise unsafe operations (which is less often a problem in optimization, but it does not hurt to check). Code analysis tools can also help you to enforce best practices, which are good for performance, readability, and maintainability. They can push you to properly format and document your code, which, too, improve readability, maintainability, and usability. They even can detect a set of well-known and frequently-occurring bugs. We therefore also run a variety of such tools on our code base, including (in alphabetical order):

  • autoflake, a tool for finding unused imports and variables

  • bandit, a linter for finding security issues

  • dodgy, for checking for dodgy looking values in the code

  • flake8, a collection of linters

  • flake8-bugbear, for finding common bugs

  • flake8-eradicate, for finding commented-out code

  • flake8-use-fstring, for checking the correct use of f-strings

  • mypy, for checking types and type annotations

  • pycodestyle, for checking the formatting and coding style of the source

  • pydocstyle, for checking the format of the docstrings

  • pyflakes, for detecting some errors in the code

  • pylint, another static analysis tool

  • pyroma, for checking whether the code complies with various best practices

  • ruff, a static analysis tool checking a wide range of coding conventions

  • semgrep, another static analyzer for finding bugs and problems

  • tryceratops, for checking against exception handling anti-patterns

  • unimport, for checking against unused import statements

  • vulture, for finding dead code

On git pushes, GitHub also automatically runs CodeQL to check for common vulnerabilities and coding errors. We also turned on GitHub’s private vulnerability reporting and the Dependabot vulnerability and security alerts.

Using all of these tools increases the build time. However, combined with thorough unit testing and documentation, it should help to prevent bugs, to improve readability, maintainability, and usability of the code. It does not matter whether we are doing research or try to solve practical problems in the industry — we should always strive to make good software with high code quality.

Often, researchers in particular think that hacking something together that works is enough, that documentation is unimportant, that code style best practices can be ignored, and so on. And then they wonder why they cannot understand their own code a few years down the line (at least, this happened to me in the past…). Or why no one can use their code to build atop of their research (which is the normal case for me).

Improving code quality can never come later. We always must maintain high coding and documentation standards from the very beginning. While moptipy may still be far from achieving these goals, at least we try to get there.

Anyway, you can find our full build script running all the tests, doing all the static analyses, creating the documentation, and creating and packaging the distribution files in the repository, too. Besides the basic moptipyapps dependencies, it requires a set of additional dependencies. These are all automatically installed during the build procedure. The build only works under Linux.

5. License

moptipyapps is a library for implementing, using, and experimenting with metaheuristic optimization algorithms. Our project is developed for scientific, educational, and industrial applications.

Copyright (C) 2023 Thomas Weise (汤卫思教授)

Dr. Thomas Weise (see Contact) holds the copyright of this package except for the data of the benchmark sets we imported from other sources. moptipyapps is provided to the public as open source software under the GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007. Terms for other licenses, e.g., for specific industrial applications, can be negotiated with Dr. Thomas Weise (who can be reached via the contact information below).

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.

Please visit the contributions guidelines for moptipy if you would like to contribute to our package. If you have any concerns regarding security, please visit our security policy.

5.1. Exceptions

  • Most of the included benchmark instance data of the two-dimensional bin packing problem is taken from 2DPackLib. It has been stored in a more size-efficient way and some unnecessary information has been stripped from it (as we really only need the raw bin packing data). Nevertheless, the copyright of the original data lies with the authors 2DPackLib or the original authors of the datasets used by them.

  • The included benchmark instances for the Traveling Salesperson Problem are taken from TSPLib. The copyright of the original data lies with Gerhard Reinelt, the original author of TSPLib, or the original authors of the datasets used by him.

  • The included benchmark instances for the Traveling Tournament Problem are taken from RobinX. The copyright of the original data lies with the authors of the dataset, presumably D. Van Bulck, D. Goossens, J. Schönberger, and M. Guajardo.

  • The included benchmark instances for the Quadratic Assignment Problem are taken from QAPLib, which is available at https://qaplib.mgi.polymtl.ca and https://coral.ise.lehigh.edu/data-sets/qaplib. The copyright of the original repository lies with R.E. Burkard, E. Çela, S.E. Karisch and F. Rendl as well as Peter Hahn and Miguel Anjos.

6. Contact

If you have any questions or suggestions, please contact Prof. Dr. Thomas Weise (汤卫思教授) of the Institute of Applied Optimization (应用优化研究所, IAO) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) via email to tweise@hfuu.edu.cn with CC to tweise@ustc.edu.cn.

diff --git a/SECURITY_md.html b/SECURITY_md.html new file mode 100755 index 00000000..65309811 --- /dev/null +++ b/SECURITY_md.html @@ -0,0 +1 @@ +Security Policy of moptipy

Security Policy of moptipy

1. Introduction

Thank you for supporting the moptipyapps, the applications using the Metaheuristic Optimization in Python (moptipy) package. moptipy is a library for implementing, using, and experimenting with metaheuristic optimization algorithms. Our project is developed for scientific, educational, and industrial applications. In this package here, you can find programs and codes using moptipy.

moptipy and moptipyapps should only be used as components in safe and secure environments. None of its API should be exposed via the network or in any other way that might constitute a security risk. Nevertheless, we take the security of our moptipy and moptipyapps seriously.

2. Reporting of Issues

If you believe you have found a security vulnerability in moptipyapps, please report it to us privately. Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests. Instead, please send an email to tweise@hfuu.edu.cn with CC to tweise@ustc.edu.cn.

Please include as much of the information listed below as you can to help us better understand and resolve the issue:

  • The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting)
  • Full paths of source file(s) related to the manifestation of the issue
  • The location of the affected source code (tag/branch/commit or direct URL)
  • Any special configuration required to reproduce the issue
  • Step-by-step instructions to reproduce the issue
  • Proof-of-concept or exploit code (if possible)
  • Impact of the issue, including how an attacker might exploit the issue

This information will help us triage your report more quickly.

diff --git a/_modules/.nojekyll b/_modules/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 00000000..599fd699 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1 @@ +Overview: module code — moptipyapps 0.8.62 documentation

All modules for which code is available

diff --git a/_modules/moptipyapps/.nojekyll b/_modules/moptipyapps/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/moptipyapps/binpacking2d/.nojekyll b/_modules/moptipyapps/binpacking2d/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/moptipyapps/binpacking2d/bks.html b/_modules/moptipyapps/binpacking2d/bks.html new file mode 100644 index 00000000..1a34401a --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/bks.html @@ -0,0 +1,1137 @@ +moptipyapps.binpacking2d.bks — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.bks

+"""In this file, we provide a set of best-known solutions for the 2D-BPP."""
+
+from dataclasses import dataclass
+from importlib import resources  # nosem
+from math import inf, isfinite
+from re import Pattern
+from re import compile as p_compile
+from statistics import mean
+from typing import Any, Callable, Final, Iterable, Mapping, cast
+
+from pycommons.ds.immutable_map import immutable_mapping
+from pycommons.io.path import UTF8
+from pycommons.math.int_math import try_int, try_int_mul
+from pycommons.strings.chars import WHITESPACE
+from pycommons.strings.tools import replace_regex
+from pycommons.types import check_int_range
+
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.packing_result import (
+    BIN_COUNT_NAME,
+    PackingResult,
+)
+
+
+def __load_references() -> Mapping[str, str]:
+    """
+    Load the references from the BibTeX resource.
+
+    This is not a fully-fledged BibTeX parser. It is a very simple and crude
+    method to extract bibliography keys and data.
+
+    :return: the immutable reference dictionary, with keys sorted
+        alphabetically
+    """
+    current_lines: list[str] = []
+    key: str | None = None
+    mode: int = 0
+    found: Final[dict[str, str]] = {}
+    single_ws: Final[Pattern] = p_compile(f"[{WHITESPACE}]")
+    multi_ws: Final[Pattern] = p_compile(f"[{WHITESPACE}][{WHITESPACE}]+")
+    with resources.files(__package__).joinpath(
+            "bks.bib").open("r", encoding=UTF8) as stream:
+        for rline in stream:
+            line: str = str.rstrip(rline)
+            if str.__len__(line) <= 0:
+                continue
+            if mode == 0:
+                line = replace_regex(single_ws, "", line)
+                if str.startswith(line, "@"):
+                    mode = 1
+                    start_idx: int = line.index("{")
+                    end_idx: int = line.index(",", start_idx + 1)
+                    key = str.strip(line[start_idx + 1:end_idx])
+                    if str.__len__(key) <= 0:
+                        raise ValueError(f"Invalid key {key!r} in {line!r}.")
+                    current_lines.append(line)
+                    if key in found:
+                        raise ValueError(f"Duplicate key {key!r}.")
+            elif mode == 1:
+                if str.strip(line) in ("}", "},"):
+                    mode = 0
+                    current_lines.append("}")
+                    found[key] = str.strip("\n".join(current_lines))
+                    current_lines.clear()
+                else:
+                    current_lines.append(replace_regex(multi_ws, " ", line))
+    if dict.__len__(found) <= 0:
+        raise ValueError("Found no references!")
+    return immutable_mapping({k: found[k] for k in sorted(found.keys())})
+
+
+#: the references to literature
+_REFERENCES: Final[Mapping[str, str]] = __load_references()
+
+#: a constant for no reference
+NO_REF: Final[str] = "{{NO_REF}}"
+
+
+
+[docs] +@dataclass(frozen=True, init=False, order=True, eq=True) +class Element: + """The algorithm or instance group specification.""" + + #: the name or name prefix + name: str + #: the name suffix, if any, and the empty string otherwise + name_suffix: str + #: the reference to the related work + reference: str + + def __init__(self, name: str, + name_suffix: str, reference: str = NO_REF) -> None: + """ + Initialize this element. + + :param name: the name or name prefix + :param name_suffix: the name suffix, or an empty string + :param reference: the reference + """ + name = str.strip(name) + if str.__len__(name) <= 0: + raise ValueError(f"Invalid name {name!r}.") + object.__setattr__(self, "name", name) + object.__setattr__(self, "name_suffix", str.strip(name_suffix)) + reference = str.strip(reference) + for ref in reference.split(","): + if ref == NO_REF: + continue + if ref not in _REFERENCES: + raise ValueError(f"Invalid reference {ref!r}.") + object.__setattr__(self, "reference", reference) + +
+[docs] + def get_bibtex(self) -> Iterable[str]: + """ + Get the BibTeX for this element. + + :return: the BibTeX string + """ + return (_REFERENCES[r] for r in self.reference.split(","))
+
+ + + +#: the BRKGA for 2D bin packing with rotation +BRKGA_BPP_2R: Final[Element] = Element( + "BRKGA", "BRKGABPPRTR", "GR2013ABRKGAF2A3BPP") +#: the BRKGA for 2D bin packing without rotation +BRKGA_BPP_ANB: Final[Element] = Element( + "BRKGA", "BRKGABPPRANB", "GR2013ABRKGAF2A3BPP") +#: the grasp/vnd for 2D bin packing without rotation +GRASP_VND: Final[Element] = Element( + "GRASPVNDGRASP", "GRASPVNDVND", "PAVOT2010AHGVAFTATDBP") +#: the price-and-cut algorithm for 2D bin packing with rotation +PAC: Final[Element] = Element("PAC", "", "CGRS2020PACATSMTOOSFT2BPP") +#: the HHANO-R algorithm for 2D bin packing with rotation +HHANO_R: Final[Element] = Element("HHANO", "HHANOR", "BDC2015RHHAFTOONO2BPP") +#: the HHANO-SR algorithm for 2D bin packing with rotation +HHANO_SR: Final[Element] = Element("HHANO", "HHANOSR", "BDC2015RHHAFTOONO2BPP") +#: the MXGA algorithm for 2D bin packing with rotation +MXGA: Final[Element] = Element("MXGA", "", "L2008AGAFTDBPP") +#: the LGFi algorithm +EALGFI: Final[Element] = Element( + "EALGFIEA", "EALGFILGFI", "BS2013ST2BPPBMOAHEA") + +#: the small A instances +A_SMALL: Final[Element] = Element("a", "small", "MAVdC2010AFMFTTDGCSP") +#: the median sized A instances +A_MED: Final[Element] = Element("a", "med", "MAVdC2010AFMFTTDGCSP") +#: the large A instances +A_LARGE: Final[Element] = Element("a", "large", "MAVdC2010AFMFTTDGCSP") +#: the asqas +ASQAS: Final[Element] = Element("asqas", "", "vdBBMSB2016ASIASSTFI") +#: the first part of the Beng instances +BENG_1_8: Final[Element] = Element("beng", "1-8", "B1982PRPAHA") +#: the second part of the Beng instances +BENG_9_10: Final[Element] = Element("beng", "9-10", "B1982PRPAHA") +#: the class 1-20 benchmarks +CLASS_1_20: Final[Element] = Element( + "class 1", "20", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 1-40 benchmarks +CLASS_1_40: Final[Element] = Element( + "class 1", "40", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 1-60 benchmarks +CLASS_1_60: Final[Element] = Element( + "class 1", "60", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 1-80 benchmarks +CLASS_1_80: Final[Element] = Element( + "class 1", "80", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 1-100 benchmarks +CLASS_1_100: Final[Element] = Element( + "class 1", "100", "BW1987TDFBPA,MV1998ESOTTDFBPP") + +#: the class 2-20 benchmarks +CLASS_2_20: Final[Element] = Element( + "class 2", "20", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 2-40 benchmarks +CLASS_2_40: Final[Element] = Element( + "class 2", "40", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 2-60 benchmarks +CLASS_2_60: Final[Element] = Element( + "class 2", "60", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 2-80 benchmarks +CLASS_2_80: Final[Element] = Element( + "class 2", "80", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 1-100 benchmarks +CLASS_2_100: Final[Element] = Element( + "class 2", "100", "BW1987TDFBPA,MV1998ESOTTDFBPP") + +#: the class 3-20 benchmarks +CLASS_3_20: Final[Element] = Element( + "class 3", "20", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 1-40 benchmarks +CLASS_3_40: Final[Element] = Element( + "class 3", "40", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 3-60 benchmarks +CLASS_3_60: Final[Element] = Element( + "class 3", "60", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 3-80 benchmarks +CLASS_3_80: Final[Element] = Element( + "class 3", "80", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 3-100 benchmarks +CLASS_3_100: Final[Element] = Element( + "class 3", "100", "BW1987TDFBPA,MV1998ESOTTDFBPP") + +#: the class 4-20 benchmarks +CLASS_4_20: Final[Element] = Element( + "class 4", "20", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 4-40 benchmarks +CLASS_4_40: Final[Element] = Element( + "class 4", "40", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 4-60 benchmarks +CLASS_4_60: Final[Element] = Element( + "class 4", "60", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 4-80 benchmarks +CLASS_4_80: Final[Element] = Element( + "class 4", "80", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 4-100 benchmarks +CLASS_4_100: Final[Element] = Element( + "class 4", "100", "BW1987TDFBPA,MV1998ESOTTDFBPP") + +#: the class 5-20 benchmarks +CLASS_5_20: Final[Element] = Element( + "class 5", "20", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 5-40 benchmarks +CLASS_5_40: Final[Element] = Element( + "class 5", "40", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 5-60 benchmarks +CLASS_5_60: Final[Element] = Element( + "class 5", "60", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 5-80 benchmarks +CLASS_5_80: Final[Element] = Element( + "class 5", "80", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 5-100 benchmarks +CLASS_5_100: Final[Element] = Element( + "class 5", "100", "BW1987TDFBPA,MV1998ESOTTDFBPP") + +#: the class 6-20 benchmarks +CLASS_6_20: Final[Element] = Element( + "class 6", "20", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 6-40 benchmarks +CLASS_6_40: Final[Element] = Element( + "class 6", "40", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 6-60 benchmarks +CLASS_6_60: Final[Element] = Element( + "class 6", "60", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 6-80 benchmarks +CLASS_6_80: Final[Element] = Element( + "class 6", "80", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 6-100 benchmarks +CLASS_6_100: Final[Element] = Element( + "class 6", "100", "BW1987TDFBPA,MV1998ESOTTDFBPP") + +#: the class 7-20 benchmarks +CLASS_7_20: Final[Element] = Element( + "class 7", "20", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 7-40 benchmarks +CLASS_7_40: Final[Element] = Element( + "class 7", "40", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 7-60 benchmarks +CLASS_7_60: Final[Element] = Element( + "class 7", "60", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 7-80 benchmarks +CLASS_7_80: Final[Element] = Element( + "class 7", "80", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 7-100 benchmarks +CLASS_7_100: Final[Element] = Element( + "class 7", "100", "BW1987TDFBPA,MV1998ESOTTDFBPP") + +#: the class 8-20 benchmarks +CLASS_8_20: Final[Element] = Element( + "class 8", "20", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 8-40 benchmarks +CLASS_8_40: Final[Element] = Element( + "class 8", "40", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 8-60 benchmarks +CLASS_8_60: Final[Element] = Element( + "class 8", "60", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 8-80 benchmarks +CLASS_8_80: Final[Element] = Element( + "class 8", "80", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 8-100 benchmarks +CLASS_8_100: Final[Element] = Element( + "class 8", "100", "BW1987TDFBPA,MV1998ESOTTDFBPP") + +#: the class 9-20 benchmarks +CLASS_9_20: Final[Element] = Element( + "class 9", "20", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 9-40 benchmarks +CLASS_9_40: Final[Element] = Element( + "class 9", "40", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 9-60 benchmarks +CLASS_9_60: Final[Element] = Element( + "class 9", "60", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 9-80 benchmarks +CLASS_9_80: Final[Element] = Element( + "class 9", "80", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 9-100 benchmarks +CLASS_9_100: Final[Element] = Element( + "class 9", "100", "BW1987TDFBPA,MV1998ESOTTDFBPP") + +#: the class 10-20 benchmarks +CLASS_10_20: Final[Element] = Element( + "class 10", "20", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 10-40 benchmarks +CLASS_10_40: Final[Element] = Element( + "class 10", "40", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 10-60 benchmarks +CLASS_10_60: Final[Element] = Element( + "class 10", "60", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 10-80 benchmarks +CLASS_10_80: Final[Element] = Element( + "class 10", "80", "BW1987TDFBPA,MV1998ESOTTDFBPP") +#: the class 6-100 benchmarks +CLASS_10_100: Final[Element] = Element( + "class 10", "100", "BW1987TDFBPA,MV1998ESOTTDFBPP") + + +def __sort_key(txt: str) -> list[str]: + """ + Get a sort key from a string. + + :param txt: the string + :return: the sort key + """ + kys: list[str] = [] + for v in txt.split(","): + for vv in v.split("-"): + for vvv in vv.split("_"): + for vvvv in vvv.split(" "): + if vvvv == "small": + kys.append("1_small") + elif vvvv == "med": + kys.append("2_med") + elif vvvv == "large": + kys.append("3_large") + elif vvvv.startswith("asqas"): + kys.append(f"zzzzzzzzzzzzzzzzzz{vvvv}") + else: + kys.append(vvvv.rjust(10, "0") + if vvvv.isdigit() else vvvv) + return kys + + +#: The set of class and beng instance groups +CLASS_AND_BENG: Final[tuple[Element, ...]] = ( + BENG_1_8, BENG_9_10, CLASS_1_20, CLASS_1_40, CLASS_1_60, CLASS_1_80, + CLASS_1_100, CLASS_2_20, CLASS_2_40, CLASS_2_60, CLASS_2_80, + CLASS_2_100, CLASS_3_20, CLASS_3_40, CLASS_3_60, CLASS_3_80, + CLASS_3_100, CLASS_4_20, CLASS_4_40, CLASS_4_60, CLASS_4_80, + CLASS_4_100, CLASS_5_20, CLASS_5_40, CLASS_5_60, CLASS_5_80, + CLASS_5_100, CLASS_6_20, CLASS_6_40, CLASS_6_60, CLASS_6_80, + CLASS_6_100, CLASS_7_20, CLASS_7_40, CLASS_7_60, CLASS_7_80, + CLASS_7_100, CLASS_8_20, CLASS_8_40, CLASS_8_60, CLASS_8_80, + CLASS_8_100, CLASS_9_20, CLASS_9_40, CLASS_9_60, CLASS_9_80, + CLASS_9_100, CLASS_10_20, CLASS_10_40, CLASS_10_60, CLASS_10_80, + CLASS_10_100, +) + + +def __make_instance_map() -> Mapping[Element, tuple[str, ...]]: + """ + Make an instance map. + + :return: the instance map. + """ + groups: Final[list[Element]] = [ + A_SMALL, A_MED, A_LARGE, ASQAS, + BENG_1_8, BENG_9_10, CLASS_1_20, CLASS_1_40, CLASS_1_60, CLASS_1_80, + CLASS_1_100, CLASS_2_20, CLASS_2_40, CLASS_2_60, CLASS_2_80, + CLASS_2_100, CLASS_3_20, CLASS_3_40, CLASS_3_60, CLASS_3_80, + CLASS_3_100, CLASS_4_20, CLASS_4_40, CLASS_4_60, CLASS_4_80, + CLASS_4_100, CLASS_5_20, CLASS_5_40, CLASS_5_60, CLASS_5_80, + CLASS_5_100, CLASS_6_20, CLASS_6_40, CLASS_6_60, CLASS_6_80, + CLASS_6_100, CLASS_7_20, CLASS_7_40, CLASS_7_60, CLASS_7_80, + CLASS_7_100, CLASS_8_20, CLASS_8_40, CLASS_8_60, CLASS_8_80, + CLASS_8_100, CLASS_9_20, CLASS_9_40, CLASS_9_60, CLASS_9_80, + CLASS_9_100, CLASS_10_20, CLASS_10_40, CLASS_10_60, CLASS_10_80, + CLASS_10_100] + + data: dict[Element, tuple[str, ...]] = {} + for group in Instance.list_resources_groups(): + prefix: str = group[0] + suffix: str = "" if group[1] is None else group[1] + insts: tuple[str, ...] = group[2] + found: bool = False + for ggg in groups: + if (ggg.name == prefix) and (ggg.name_suffix == suffix): + if ggg in data: + raise ValueError(f"Encountered {ggg} twice?") + data[ggg] = insts + found = True + break + if not found: + raise ValueError(f"Did not find {group!r}.") + + return immutable_mapping({k: data[k] for k in sorted( + data.keys(), key=lambda e: __sort_key(f"{e.name} {e.name_suffix}"))}) + + +#: a mapping of instance groups to instances +GROUPS_TO_INSTANCES: Final[Mapping[Element, tuple[ + str, ...]]] = __make_instance_map() + +#: A sort key function for instance groups +INST_GROUP_SORT_KEY: Final[Callable[[Element], int]] \ + = list(GROUPS_TO_INSTANCES.keys()).index + + +def __make_instance_to_groups() -> Mapping[str, Element]: + """ + Make a mapping of instances to groups. + + :return: the mapping of instances to groups. + """ + data: list[tuple[str, Element]] = [( + v, k) for k, vv in GROUPS_TO_INSTANCES.items() for v in vv] + data.sort(key=lambda x: __sort_key(x[0])) + return immutable_mapping(dict(data)) + + +#: a mapping of instances to instance groups +INSTANCES_TO_GROUPS: Final[Mapping[str, Element]] = \ + __make_instance_to_groups() + + +def __lb_avg_denormalize( + with_rotation: bool, algo: Element, group: Element, + value: int | float, min_result: int = -1_000_000) -> tuple[ + bool, Element, Element, int]: + """ + De-normalize using the maximum of the Dell'Amico and geometric bound. + + :param with_rotation: is this a result with or without rotation? + :param algo: the algorithm used + :param group: the instance group + :param value: the value + :param min_result: the minimum denormalized result + :return: de-normalized average + """ + return (with_rotation, algo, group, max( + min_result, int(round(try_int_mul(sum(Instance.from_resource( + ins).lower_bound_bins for ins in GROUPS_TO_INSTANCES[group]), + value))))) + + +#: the related works with rotation averaged +__RW__AVERAGE: Final[tuple[tuple[ + bool, Element, Element, int], ...]] = ( + (True, BRKGA_BPP_2R, CLASS_1_20, 66), + (False, BRKGA_BPP_ANB, CLASS_1_20, 71), + (True, BRKGA_BPP_2R, CLASS_1_40, 128), + (False, BRKGA_BPP_ANB, CLASS_1_40, 134), + (True, BRKGA_BPP_2R, CLASS_1_60, 195), + (False, BRKGA_BPP_ANB, CLASS_1_60, 200), + (True, BRKGA_BPP_2R, CLASS_1_80, 270), + (False, BRKGA_BPP_ANB, CLASS_1_80, 275), + (True, BRKGA_BPP_2R, CLASS_1_100, 313), + (False, BRKGA_BPP_ANB, CLASS_1_100, 317), + (True, BRKGA_BPP_2R, CLASS_2_20, 10), + (False, BRKGA_BPP_ANB, CLASS_2_20, 10), + (True, BRKGA_BPP_2R, CLASS_2_40, 19), + (False, BRKGA_BPP_ANB, CLASS_2_40, 19), + (True, BRKGA_BPP_2R, CLASS_2_60, 25), + (False, BRKGA_BPP_ANB, CLASS_2_60, 25), + (True, BRKGA_BPP_2R, CLASS_2_80, 31), + (False, BRKGA_BPP_ANB, CLASS_2_80, 31), + (True, BRKGA_BPP_2R, CLASS_2_100, 39), + (False, BRKGA_BPP_ANB, CLASS_2_100, 39), + (True, BRKGA_BPP_2R, CLASS_3_20, 47), + (False, BRKGA_BPP_ANB, CLASS_3_20, 51), + (True, BRKGA_BPP_2R, CLASS_3_40, 92), + (False, BRKGA_BPP_ANB, CLASS_3_40, 94), + (True, BRKGA_BPP_2R, CLASS_3_60, 134), + (False, BRKGA_BPP_ANB, CLASS_3_60, 139), + (True, BRKGA_BPP_2R, CLASS_3_80, 182), + (False, BRKGA_BPP_ANB, CLASS_3_80, 189), + (True, BRKGA_BPP_2R, CLASS_3_100, 220), + (False, BRKGA_BPP_ANB, CLASS_3_100, 223), + (True, BRKGA_BPP_2R, CLASS_4_20, 10), + (False, BRKGA_BPP_ANB, CLASS_4_20, 10), + (True, BRKGA_BPP_2R, CLASS_4_40, 19), + (False, BRKGA_BPP_ANB, CLASS_4_40, 19), + (True, BRKGA_BPP_2R, CLASS_4_60, 23), + (False, BRKGA_BPP_ANB, CLASS_4_60, 25), + (True, BRKGA_BPP_2R, CLASS_4_80, 31), + (False, BRKGA_BPP_ANB, CLASS_4_80, 31), + (True, BRKGA_BPP_2R, CLASS_4_100, 37), + (False, BRKGA_BPP_ANB, CLASS_4_100, 37), + (True, BRKGA_BPP_2R, CLASS_5_20, 59), + (False, BRKGA_BPP_ANB, CLASS_5_20, 65), + (True, BRKGA_BPP_2R, CLASS_5_40, 114), + (False, BRKGA_BPP_ANB, CLASS_5_40, 119), + (True, BRKGA_BPP_2R, CLASS_5_60, 172), + (False, BRKGA_BPP_ANB, CLASS_5_60, 180), + (True, BRKGA_BPP_2R, CLASS_5_80, 239), + (False, BRKGA_BPP_ANB, CLASS_5_80, 247), + (True, BRKGA_BPP_2R, CLASS_5_100, 277), + (False, BRKGA_BPP_ANB, CLASS_5_100, 281), + (True, BRKGA_BPP_2R, CLASS_6_20, 10), + (False, BRKGA_BPP_ANB, CLASS_6_20, 10), + (True, BRKGA_BPP_2R, CLASS_6_40, 16), + (False, BRKGA_BPP_ANB, CLASS_6_40, 16), + (True, BRKGA_BPP_2R, CLASS_6_60, 21), + (False, BRKGA_BPP_ANB, CLASS_6_60, 21), + (True, BRKGA_BPP_2R, CLASS_6_80, 30), + (False, BRKGA_BPP_ANB, CLASS_6_80, 30), + (True, BRKGA_BPP_2R, CLASS_6_100, 32), + (False, BRKGA_BPP_ANB, CLASS_6_100, 33), + (True, BRKGA_BPP_2R, CLASS_7_20, 52), + (False, BRKGA_BPP_ANB, CLASS_7_20, 55), + (True, BRKGA_BPP_2R, CLASS_7_40, 102), + (False, BRKGA_BPP_ANB, CLASS_7_40, 111), + (True, BRKGA_BPP_2R, CLASS_7_60, 146), + (False, BRKGA_BPP_ANB, CLASS_7_60, 158), + (True, BRKGA_BPP_2R, CLASS_7_80, 208), + (False, BRKGA_BPP_ANB, CLASS_7_80, 232), + (True, BRKGA_BPP_2R, CLASS_7_100, 250), + (False, BRKGA_BPP_ANB, CLASS_7_100, 271), + (True, BRKGA_BPP_2R, CLASS_8_20, 53), + (False, BRKGA_BPP_ANB, CLASS_8_20, 58), + (True, BRKGA_BPP_2R, CLASS_8_40, 103), + (False, BRKGA_BPP_ANB, CLASS_8_40, 113), + (True, BRKGA_BPP_2R, CLASS_8_60, 147), + (False, BRKGA_BPP_ANB, CLASS_8_60, 161), + (True, BRKGA_BPP_2R, CLASS_8_80, 204), + (False, BRKGA_BPP_ANB, CLASS_8_80, 224), + (True, BRKGA_BPP_2R, CLASS_8_100, 252), + (False, BRKGA_BPP_ANB, CLASS_8_100, 278), + (True, BRKGA_BPP_2R, CLASS_9_20, 143), + (False, BRKGA_BPP_ANB, CLASS_9_20, 143), + (True, BRKGA_BPP_2R, CLASS_9_40, 275), + (False, BRKGA_BPP_ANB, CLASS_9_40, 278), + (True, BRKGA_BPP_2R, CLASS_9_60, 435), + (False, BRKGA_BPP_ANB, CLASS_9_60, 437), + (True, BRKGA_BPP_2R, CLASS_9_80, 573), + (False, BRKGA_BPP_ANB, CLASS_9_80, 577), + (True, BRKGA_BPP_2R, CLASS_9_100, 693), + (False, BRKGA_BPP_ANB, CLASS_9_100, 695), + (True, BRKGA_BPP_2R, CLASS_10_20, 41), + (False, BRKGA_BPP_ANB, CLASS_10_20, 42), + (True, BRKGA_BPP_2R, CLASS_10_40, 72), + (False, BRKGA_BPP_ANB, CLASS_10_40, 74), + (True, BRKGA_BPP_2R, CLASS_10_60, 99), + (False, BRKGA_BPP_ANB, CLASS_10_60, 100), + (True, BRKGA_BPP_2R, CLASS_10_80, 125), + (False, BRKGA_BPP_ANB, CLASS_10_80, 128), + (True, BRKGA_BPP_2R, CLASS_10_100, 154), + (False, BRKGA_BPP_ANB, CLASS_10_100, 158), + (True, BRKGA_BPP_2R, BENG_1_8, 54), + (False, BRKGA_BPP_ANB, BENG_1_8, 54), + (True, BRKGA_BPP_2R, BENG_9_10, 13), + (False, BRKGA_BPP_ANB, BENG_9_10, 13), + (False, PAC, CLASS_1_20, 71), + (False, PAC, CLASS_1_40, 134), + (False, PAC, CLASS_1_60, 200), + (False, PAC, CLASS_1_80, 275), + (False, PAC, CLASS_1_100, 317), + (False, PAC, CLASS_2_20, 10), + (False, PAC, CLASS_2_40, 19), + (False, PAC, CLASS_2_60, 25), + (False, PAC, CLASS_2_80, 31), + (False, PAC, CLASS_2_100, 39), + (False, PAC, CLASS_3_20, 51), + (False, PAC, BENG_1_8, 54), + (False, PAC, BENG_9_10, 13), + (True, PAC, CLASS_1_20, 66), + (True, PAC, CLASS_1_40, 128), + (True, PAC, CLASS_1_60, 195), + (True, PAC, CLASS_1_80, 270), + (True, PAC, CLASS_1_100, 313), + (True, PAC, CLASS_2_20, 10), + (True, PAC, CLASS_2_40, 19), + (True, PAC, CLASS_2_60, 25), + (True, PAC, CLASS_2_80, 31), + (True, PAC, CLASS_2_100, 39), + (True, PAC, CLASS_3_20, 47), + (True, PAC, BENG_1_8, 54), + (True, PAC, BENG_9_10, 13), + (True, HHANO_R, CLASS_1_20, 66), + (True, HHANO_SR, CLASS_1_20, 66), + (True, HHANO_R, CLASS_1_40, 131), + (True, HHANO_SR, CLASS_1_40, 129), + (True, HHANO_R, CLASS_1_60, 196), + (True, HHANO_SR, CLASS_1_60, 195), + (True, HHANO_R, CLASS_1_80, 270), + (True, HHANO_SR, CLASS_1_80, 270), + (True, HHANO_R, CLASS_1_100, 314), + (True, HHANO_SR, CLASS_1_100, 313), + (True, HHANO_R, CLASS_2_20, 10), + (True, HHANO_SR, CLASS_2_20, 10), + (True, HHANO_R, CLASS_2_40, 20), + (True, HHANO_SR, CLASS_2_40, 19), + (True, HHANO_R, CLASS_2_60, 25), + (True, HHANO_SR, CLASS_2_60, 25), + (True, HHANO_R, CLASS_2_80, 31), + (True, HHANO_SR, CLASS_2_80, 31), + (True, HHANO_R, CLASS_2_100, 39), + (True, HHANO_SR, CLASS_2_100, 39), + (True, HHANO_R, CLASS_3_20, 48), + (True, HHANO_SR, CLASS_3_20, 48), + (True, HHANO_R, CLASS_3_40, 95), + (True, HHANO_SR, CLASS_3_40, 95), + (True, HHANO_R, CLASS_3_60, 137), + (True, HHANO_SR, CLASS_3_60, 137), + (True, HHANO_R, CLASS_3_80, 186), + (True, HHANO_SR, CLASS_3_80, 187), + (True, HHANO_R, CLASS_3_100, 225), + (True, HHANO_SR, CLASS_3_100, 225), + (True, HHANO_R, CLASS_4_20, 10), + (True, HHANO_SR, CLASS_4_20, 10), + (True, HHANO_R, CLASS_4_40, 19), + (True, HHANO_SR, CLASS_4_40, 19), + (True, HHANO_R, CLASS_4_60, 25), + (True, HHANO_SR, CLASS_4_60, 25), + (True, HHANO_R, CLASS_4_80, 32), + (True, HHANO_SR, CLASS_4_80, 33), + (True, HHANO_R, CLASS_4_100, 38), + (True, HHANO_SR, CLASS_4_100, 38), + (True, HHANO_R, CLASS_5_20, 59), + (True, HHANO_SR, CLASS_5_20, 59), + (True, HHANO_R, CLASS_5_40, 116), + (True, HHANO_SR, CLASS_5_40, 115), + (True, HHANO_R, CLASS_5_60, 175), + (True, HHANO_SR, CLASS_5_60, 176), + (True, HHANO_R, CLASS_5_80, 240), + (True, HHANO_SR, CLASS_5_80, 241), + (True, HHANO_R, CLASS_5_100, 284), + (True, HHANO_SR, CLASS_5_100, 284), + (True, HHANO_R, CLASS_6_20, 10), + (True, HHANO_SR, CLASS_6_20, 10), + (True, HHANO_R, CLASS_6_40, 18), + (True, HHANO_SR, CLASS_6_40, 17), + (True, HHANO_R, CLASS_6_60, 22), + (True, HHANO_SR, CLASS_6_60, 22), + (True, HHANO_R, CLASS_6_80, 30), + (True, HHANO_SR, CLASS_6_80, 30), + (True, HHANO_R, CLASS_6_100, 34), + (True, HHANO_SR, CLASS_6_100, 34), + (True, HHANO_R, CLASS_7_20, 52), + (True, HHANO_SR, CLASS_7_20, 52), + (True, HHANO_R, CLASS_7_40, 106), + (True, HHANO_SR, CLASS_7_40, 107), + (True, HHANO_R, CLASS_7_60, 152), + (True, HHANO_SR, CLASS_7_60, 153), + (True, HHANO_R, CLASS_7_80, 216), + (True, HHANO_SR, CLASS_7_80, 217), + (True, HHANO_R, CLASS_7_100, 260), + (True, HHANO_SR, CLASS_7_100, 259), + (True, HHANO_R, CLASS_8_20, 53), + (True, HHANO_SR, CLASS_8_20, 53), + (True, HHANO_R, CLASS_8_40, 106), + (True, HHANO_SR, CLASS_8_40, 105), + (True, HHANO_R, CLASS_8_60, 155), + (True, HHANO_SR, CLASS_8_60, 154), + (True, HHANO_R, CLASS_8_80, 213), + (True, HHANO_SR, CLASS_8_80, 214), + (True, HHANO_R, CLASS_8_100, 261), + (True, HHANO_SR, CLASS_8_100, 262), + (True, HHANO_R, CLASS_9_20, 143), + (True, HHANO_SR, CLASS_9_20, 143), + (True, HHANO_R, CLASS_9_40, 275), + (True, HHANO_SR, CLASS_9_40, 275), + (True, HHANO_R, CLASS_9_60, 435), + (True, HHANO_SR, CLASS_9_60, 435), + (True, HHANO_R, CLASS_9_80, 573), + (True, HHANO_SR, CLASS_9_80, 573), + (True, HHANO_R, CLASS_9_100, 693), + (True, HHANO_SR, CLASS_9_100, 693), + (True, HHANO_R, CLASS_10_20, 41), + (True, HHANO_SR, CLASS_10_20, 41), + (True, HHANO_R, CLASS_10_40, 73), + (True, HHANO_SR, CLASS_10_40, 73), + (True, HHANO_R, CLASS_10_60, 101), + (True, HHANO_SR, CLASS_10_60, 101), + (True, HHANO_R, CLASS_10_80, 129), + (True, HHANO_SR, CLASS_10_80, 130), + (True, HHANO_R, CLASS_10_100, 161), + (True, HHANO_SR, CLASS_10_100, 162), + __lb_avg_denormalize(True, MXGA, CLASS_1_20, 1.03), + __lb_avg_denormalize(True, MXGA, CLASS_1_40, 1.04), + __lb_avg_denormalize(True, MXGA, CLASS_1_60, 1.04, 195), # 19.4 + __lb_avg_denormalize(True, MXGA, CLASS_1_80, 1.06), + __lb_avg_denormalize(True, MXGA, CLASS_1_100, 1.02, 313), # 31.2 + __lb_avg_denormalize(True, MXGA, CLASS_2_20, 1.0), + __lb_avg_denormalize(True, MXGA, CLASS_2_40, 1.1), + __lb_avg_denormalize(True, MXGA, CLASS_2_60, 1.0), + __lb_avg_denormalize(True, MXGA, CLASS_2_80, 1.0), + __lb_avg_denormalize(True, MXGA, CLASS_2_100, 1.0), + __lb_avg_denormalize(True, MXGA, CLASS_3_20, 1.04), + __lb_avg_denormalize(True, MXGA, CLASS_3_40, 1.09), + __lb_avg_denormalize(True, MXGA, CLASS_3_60, 1.08), + __lb_avg_denormalize(True, MXGA, CLASS_3_80, 1.06), + __lb_avg_denormalize(True, MXGA, CLASS_3_100, 1.06), + __lb_avg_denormalize(True, MXGA, CLASS_4_20, 1.0), + __lb_avg_denormalize(True, MXGA, CLASS_4_40, 1.0), + __lb_avg_denormalize(True, MXGA, CLASS_4_60, 1.10), + __lb_avg_denormalize(True, MXGA, CLASS_4_80, 1.07), + __lb_avg_denormalize(True, MXGA, CLASS_4_100, 1.03), + __lb_avg_denormalize(True, MXGA, CLASS_5_20, 1.04), + __lb_avg_denormalize(True, MXGA, CLASS_5_40, 1.06), + __lb_avg_denormalize(True, MXGA, CLASS_5_60, 1.07), + __lb_avg_denormalize(True, MXGA, CLASS_5_80, 1.07), + __lb_avg_denormalize(True, MXGA, CLASS_5_100, 1.05), + __lb_avg_denormalize(True, MXGA, CLASS_6_20, 1.0), + __lb_avg_denormalize(True, MXGA, CLASS_6_40, 1.4), + __lb_avg_denormalize(True, MXGA, CLASS_6_60, 1.0), + __lb_avg_denormalize(True, MXGA, CLASS_6_80, 1.0), + __lb_avg_denormalize(True, MXGA, CLASS_6_100, 1.07), + __lb_avg_denormalize(True, MXGA, CLASS_7_20, 1.11), + __lb_avg_denormalize(True, MXGA, CLASS_7_40, 1.07), + __lb_avg_denormalize(True, MXGA, CLASS_7_60, 1.05), + __lb_avg_denormalize(True, MXGA, CLASS_7_80, 1.08), + __lb_avg_denormalize(True, MXGA, CLASS_7_100, 1.07), + __lb_avg_denormalize(True, MXGA, CLASS_8_20, 1.10), + __lb_avg_denormalize(True, MXGA, CLASS_8_40, 1.09), + __lb_avg_denormalize(True, MXGA, CLASS_8_60, 1.06), + __lb_avg_denormalize(True, MXGA, CLASS_8_80, 1.07), + __lb_avg_denormalize(True, MXGA, CLASS_8_100, 1.06), + __lb_avg_denormalize(True, MXGA, CLASS_9_20, 1.0), + __lb_avg_denormalize(True, MXGA, CLASS_9_40, 1.01), + __lb_avg_denormalize(True, MXGA, CLASS_9_60, 1.01), + __lb_avg_denormalize(True, MXGA, CLASS_9_80, 1.01), + __lb_avg_denormalize(True, MXGA, CLASS_9_100, 1.01), + __lb_avg_denormalize(True, MXGA, CLASS_10_20, 1.13), + __lb_avg_denormalize(True, MXGA, CLASS_10_40, 1.06), + __lb_avg_denormalize(True, MXGA, CLASS_10_60, 1.07), + __lb_avg_denormalize(True, MXGA, CLASS_10_80, 1.06), + __lb_avg_denormalize(True, MXGA, CLASS_10_100, 1.04), + (False, EALGFI, CLASS_1_20, 71), + (False, EALGFI, CLASS_1_40, 134), + (False, EALGFI, CLASS_1_60, 200), + (False, EALGFI, CLASS_1_80, 275), + (False, EALGFI, CLASS_1_100, 317), + (False, EALGFI, CLASS_2_20, 10), + (False, EALGFI, CLASS_2_40, 19), + (False, EALGFI, CLASS_2_60, 25), + (False, EALGFI, CLASS_2_80, 31), + (False, EALGFI, CLASS_2_100, 39), + (False, EALGFI, CLASS_3_20, 51), + (False, EALGFI, CLASS_3_40, 94), + (False, EALGFI, CLASS_3_60, 139), + (False, EALGFI, CLASS_3_80, 189), + (False, EALGFI, CLASS_3_100, 224), + (False, EALGFI, CLASS_4_20, 10), + (False, EALGFI, CLASS_4_40, 19), + (False, EALGFI, CLASS_4_60, 23), + (False, EALGFI, CLASS_4_80, 31), + (False, EALGFI, CLASS_4_100, 37), + (False, EALGFI, CLASS_5_20, 65), + (False, EALGFI, CLASS_5_40, 119), + (False, EALGFI, CLASS_5_60, 180), + (False, EALGFI, CLASS_5_80, 247), + (False, EALGFI, CLASS_5_100, 284), + (False, EALGFI, CLASS_6_20, 10), + (False, EALGFI, CLASS_6_40, 17), + (False, EALGFI, CLASS_6_60, 21), + (False, EALGFI, CLASS_6_80, 30), + (False, EALGFI, CLASS_6_100, 32), + (False, EALGFI, CLASS_7_20, 55), + (False, EALGFI, CLASS_7_40, 111), + (False, EALGFI, CLASS_7_60, 159), + (False, EALGFI, CLASS_7_80, 232), + (False, EALGFI, CLASS_7_100, 271), + (False, EALGFI, CLASS_8_20, 58), + (False, EALGFI, CLASS_8_40, 113), + (False, EALGFI, CLASS_8_60, 161), + (False, EALGFI, CLASS_8_80, 224), + (False, EALGFI, CLASS_8_100, 277), + (False, EALGFI, CLASS_9_20, 143), + (False, EALGFI, CLASS_9_40, 278), + (False, EALGFI, CLASS_9_60, 437), + (False, EALGFI, CLASS_9_80, 577), + (False, EALGFI, CLASS_9_100, 695), + (False, EALGFI, CLASS_10_20, 42), + (False, EALGFI, CLASS_10_40, 74), + (False, EALGFI, CLASS_10_60, 101), + (False, EALGFI, CLASS_10_80, 128), + (False, EALGFI, CLASS_10_100, 160), + (False, GRASP_VND, CLASS_1_20, 71), + (False, GRASP_VND, CLASS_1_40, 134), + (False, GRASP_VND, CLASS_1_60, 200), + (False, GRASP_VND, CLASS_1_80, 275), + (False, GRASP_VND, CLASS_1_100, 317), + (False, GRASP_VND, CLASS_2_20, 10), + (False, GRASP_VND, CLASS_2_40, 19), + (False, GRASP_VND, CLASS_2_60, 25), + (False, GRASP_VND, CLASS_2_80, 31), + (False, GRASP_VND, CLASS_2_100, 39), + (False, GRASP_VND, CLASS_3_20, 51), + (False, GRASP_VND, CLASS_3_40, 94), + (False, GRASP_VND, CLASS_3_60, 139), + (False, GRASP_VND, CLASS_3_80, 189), + (False, GRASP_VND, CLASS_3_100, 223), + (False, GRASP_VND, CLASS_4_20, 10), + (False, GRASP_VND, CLASS_4_40, 19), + (False, GRASP_VND, CLASS_4_60, 25), + (False, GRASP_VND, CLASS_4_80, 31), + (False, GRASP_VND, CLASS_4_100, 38), + (False, GRASP_VND, CLASS_5_20, 65), + (False, GRASP_VND, CLASS_5_40, 119), + (False, GRASP_VND, CLASS_5_60, 180), + (False, GRASP_VND, CLASS_5_80, 247), + (False, GRASP_VND, CLASS_5_100, 282), + (False, GRASP_VND, CLASS_6_20, 10), + (False, GRASP_VND, CLASS_6_40, 17), + (False, GRASP_VND, CLASS_6_60, 21), + (False, GRASP_VND, CLASS_6_80, 30), + (False, GRASP_VND, CLASS_6_100, 34), + (False, GRASP_VND, CLASS_7_20, 55), + (False, GRASP_VND, CLASS_7_40, 111), + (False, GRASP_VND, CLASS_7_60, 159), + (False, GRASP_VND, CLASS_7_80, 232), + (False, GRASP_VND, CLASS_7_100, 271), + (False, GRASP_VND, CLASS_8_20, 58), + (False, GRASP_VND, CLASS_8_40, 113), + (False, GRASP_VND, CLASS_8_60, 161), + (False, GRASP_VND, CLASS_8_80, 224), + (False, GRASP_VND, CLASS_8_100, 278), + (False, GRASP_VND, CLASS_9_20, 143), + (False, GRASP_VND, CLASS_9_40, 278), + (False, GRASP_VND, CLASS_9_60, 437), + (False, GRASP_VND, CLASS_9_80, 577), + (False, GRASP_VND, CLASS_9_100, 695), + (False, GRASP_VND, CLASS_10_20, 42), + (False, GRASP_VND, CLASS_10_40, 74), + (False, GRASP_VND, CLASS_10_60, 100), + (False, GRASP_VND, CLASS_10_80, 129), + (False, GRASP_VND, CLASS_10_100, 159), + (False, GRASP_VND, BENG_1_8, 54), + (False, GRASP_VND, BENG_9_10, 13), +) + + + + + + +
+[docs] +def make_comparison_table_data( + data: Iterable[PackingResult], + with_rotation: bool, + algo_from_pr: Callable[[PackingResult], Element] = lambda x: Element( + x.end_result.algorithm, x.end_result.objective), + algo_sort_key: Callable[[Element], Any] = lambda x: ( + -1 if (x.reference == NO_REF) else 1, x), + rw_algo_selector: Callable[[Element], bool] = lambda _: True, + aggregator: Callable[[Iterable[int | float]], int | float] = mean, + additional: Callable[[Element], Iterable[tuple[ + Element, Callable[[Iterable[int | float]], int | float]]]] = + lambda _: ()) -> tuple[tuple[Element, ...], tuple[tuple[ + Element, tuple[int | float | None, ...]], ...]]: + """ + Create the data for an end result comparison table. + + :param data: the source data + :param with_rotation: are we doing stuff with rotation? + :param algo_from_pr: convert a packing result to an algorithm + :param algo_sort_key: the algorithm sort key + :param rw_algo_selector: the related work algorithm selector + :param aggregator: the routine for per-instance averaging of bins + :param additional: an additional column constructor + + :return: the table data: the title row columns followed by the data + row-by-row, each row leading with an instance group identifier + """ + per_algo_data: dict[Element, tuple[Callable[[ + Iterable[int | float]], int | float], dict[str, list[int]]]] = {} + for pr in data: + algo: Element = algo_from_pr(pr) + inst_dict: dict[str, list[int]] + if algo in per_algo_data: + inst_dict = per_algo_data[algo][1] + else: + inst_dict = {} + per_algo_data[algo] = aggregator, inst_dict + inst: str = pr.end_result.instance + val: int = check_int_range( + pr.objectives[BIN_COUNT_NAME], BIN_COUNT_NAME, 1, 1_000_000_000) + if inst in inst_dict: + inst_dict[inst].append(val) + else: + inst_dict[inst] = [val] + + groups: Final[list[Element]] = sorted({ + INSTANCES_TO_GROUPS[k] for kk in per_algo_data.values() + for k in kk[1]}, key=INST_GROUP_SORT_KEY) + + # get the averages + algo_per_group_data: Final[dict[Element, dict[Element, int | float]]] = {} + for algo, inst_dict_and_avg in per_algo_data.items(): + dothis: list[tuple[Element, Callable[[ + Iterable[int | float]], int | float]]] = [ + (algo, inst_dict_and_avg[0])] + dothis.extend(additional(algo)) + + for xyz in dothis: + avg: Callable[[Iterable[int | float]], int | float] = xyz[1] + inst_dict = inst_dict_and_avg[1] + ag: dict[Element, int | float] = {} + algo_per_group_data[xyz[0]] = ag + algo_runs: int | None = None + for g in groups: + gis: tuple[str, ...] = GROUPS_TO_INSTANCES[g] + gisd: list[list[int | float]] = [] + ok: bool = True + for inst in gis: + if inst not in inst_dict: + ok = False + gisd.append(cast(list[int | float], inst_dict[inst])) + if ok: + if list.__len__(gisd) <= 0: + raise ValueError(f"Empty group {g!r} for {algo!r}?") + if list.__len__(gisd) != tuple.__len__(gis): + raise ValueError( + f"Some data missing in {g!r} for {algo!r}?") + ll = list.__len__(gisd[0]) + if algo_runs is None: + algo_runs = ll + elif algo_runs != ll: + raise ValueError( + f"Inconsistent number of runs for {algo!r}: " + f"{ll} in {gis[0]!r} vs. {algo_runs}.") + if not all(list.__len__(xxx) == ll for xxx in gisd): + raise ValueError( + f"Inconsistent run numbers in {g!r} for {algo!r}.") + ag[g] = int(round(sum(cast(Iterable, ( + try_int(avg(v)) for v in gisd))))) + elif len(gisd) > 0: + raise ValueError(f"Incomplete group {g!r} for {algo!r}.") + + rw: tuple[tuple[bool, Element, Element, float], ...] = get_related_work( + with_rotation=with_rotation, without_rotation=not with_rotation, + algo_select=rw_algo_selector, inst_group_select=groups.__contains__) + + algorithms: list[Element] = list({rww[1] for rww in rw}) + algorithms.sort(key=algo_sort_key) + our_algorithms = sorted(algo_per_group_data.keys(), key=algo_sort_key) + + rows: list[tuple[Element, tuple[int | float | None, ...]]] = [] + for g in groups: + xdata: list[int | float | None] = [] + for algo in algorithms: + found: bool = False + for zz in rw: + if zz[1] != algo: + continue + if zz[2] != g: + continue + found = True + xdata.append(zz[3]) + break + if found: + continue + xdata.append(None) + for algo in our_algorithms: + agz: dict[Element, int | float] = algo_per_group_data[algo] + if g not in agz: + xdata.append(None) + continue + xdata.append(agz[g]) + rows.append((g, tuple(xdata))) + + algorithms.extend(our_algorithms) + return tuple(algorithms), tuple(rows)
+ + + +
+[docs] +def make_comparison_table( + dest: Callable[[str], Any], + data: tuple[tuple[Element, ...], tuple[tuple[Element, tuple[ + int | float | None, ...]], ...]], + name_to_strs: Callable[[Element], tuple[str, str]] = lambda s: ( + s.name.split("_")[0], s.name_suffix), + format_best: Callable[[str], str] = + lambda s: f"\\textbf{{{s}}}", + count_best_over: Iterable[tuple[Iterable[Element], str]] = ()) -> None: + """ + Make a comparison table in LaTeX. + + :param dest: the destination + :param data: the source data + :param name_to_strs: the converter of names to strings + :param format_best: the formatter for best values + :param count_best_over: a set of instances to include in the best counting + and a corresponding label + """ + inst_names: Final[list[tuple[str, str]]] = [ + name_to_strs(d[0]) for d in data[1]] + inst_cols: int = 2 if any( + str.__len__(n[1]) > 0 for n in inst_names) else 1 + + dest("% begin auto-generated LaTeX table comparing " + "end results with related work.%") + writer: list[str] = [r"\begin{tabular}{@{}r"] + if inst_cols > 1: + writer.append("@{}l") + writer.extend("r" for _ in range(tuple.__len__(data[0]))) + writer.append("@{}}%") + dest("".join(writer)) + writer.clear() + dest(r"\hline%") + + head_names: Final[list[tuple[str, str]]] = [ + name_to_strs(d) for d in data[0]] + head_names.insert(0, ("instance", "group")) + if inst_cols > 1: + head_names.insert(0, ("instance", "group")) + head_count: Final[int] = list.__len__(head_names) + + for dim in (0, 1): + i = 0 + while i < head_count: + head = head_names[i] + next_i = i + 1 + while (next_i < head_count) and ( + head_names[next_i][dim] == head[dim]): + next_i += 1 + + col_txt: str = f"\\multicolumn{{{next_i - i}}}{{" + if i <= 0: + col_txt = f"{col_txt}@{{}}" + col_txt = f"{col_txt}c" + if next_i >= head_count: + col_txt = f"{col_txt}@{{}}" + col_txt = f"{col_txt}}}{{{head[dim]}}}" + if next_i >= head_count: + col_txt = f"{col_txt}\\\\%" + writer.append(col_txt) + i = next_i + dest("&".join(writer)) + writer.clear() + last_first_name = "" + + count_best: list[tuple[str, list[int]]] = [] + count_as: dict[Element, list[list[int]]] = {} + for count_this, title in count_best_over: + lst: list[int] = [0] * tuple.__len__(data[0]) + count_best.append((title, lst)) + for k in count_this: + if k in count_as: + count_as[k].append(lst) + else: + count_as[k] = [lst] + + for i, data_row in enumerate(data[1]): + inst_name: tuple[str, str] = inst_names[i] + if inst_name[0] != last_first_name: + last_first_name = inst_name[0] + dest(r"\hline%") + writer.append(last_first_name) + if inst_cols > 1: + in1: str = str.strip(inst_name[1]) + if str.__len__(in1) > 0: + in1 = str.strip(f"/{in1}") + if str.__len__(in1) <= 1: + in1 = "" + writer.append(in1) + + row_data: tuple[int | float | None, ...] = data_row[1] + minimum: int | float = inf + for ddd in row_data: + if (ddd is not None) and (ddd < minimum): + minimum = ddd + if not isfinite(minimum): + raise ValueError(f"Huuhhh? {inst_name} has non-finite min?") + instance: Element = data_row[0] + for iij, ddd in enumerate(row_data): + if ddd is None: + writer.append("") + continue + writer.append( + format_best(str(ddd)) if ddd <= minimum else str(ddd)) + if (ddd <= minimum) and (instance in count_as): + for lst in count_as[instance]: + lst[iij] += 1 + dest(f"{'&'.join(writer)}\\\\%") + writer.clear() + + dest(r"\hline%") + if list.__len__(count_best) > 0: + for brow in count_best: + writer.append( + f"\\multicolumn{{{inst_cols}}}{{@{{}}r}}{{{brow[0]}}}") + writer.extend(map(str, brow[1])) + writer[-1] = f"{writer[-1]}\\\\%" + dest("&".join(writer)) + dest(r"\hline%") + dest(r"\end{tabular}%") + dest("% end auto-generated LaTeX table comparing " + "end results with related work.%")
+ +
diff --git a/_modules/moptipyapps/binpacking2d/encodings/.nojekyll b/_modules/moptipyapps/binpacking2d/encodings/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/moptipyapps/binpacking2d/encodings/ibl_encoding_1.html b/_modules/moptipyapps/binpacking2d/encodings/ibl_encoding_1.html new file mode 100644 index 00000000..8e994303 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/encodings/ibl_encoding_1.html @@ -0,0 +1,434 @@ +moptipyapps.binpacking2d.encodings.ibl_encoding_1 — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.encodings.ibl_encoding_1

+"""
+An improved bottom left encoding by Liu and Teng extended to multiple bins.
+
+Here we provide an implementation of the improved bottom left encoding by Liu
+and Teng [1], but extended to bins with limited height. If the height of the
+bin is a limiting factor, then our implementation will automatically use
+multiple bins. Another implementation is given in
+:mod:`moptipyapps.binpacking2d.encodings.ibl_encoding_2`.
+
+An :mod:`~moptipyapps.binpacking2d.instance` of the
+two-dimensional bin packing problem defines a set of objects to be packed
+and a bin size (width and height). Each object to be packed has itself a
+width and a height as well as a repetition counter, which is `1` if the object
+only occurs a single time and larger otherwise (i.e., if the repetition
+counter is `5`, the object needs to be packaged five times).
+
+The encoding receives signed permutations with repetitions as input. Each
+element of the permutation identifies one object from the bin packing
+instance. Each such object ID must occur exactly as often as the repetition
+counter of the object in the instance data suggest. But the ID may also occur
+negated, in which case the object is supposed to rotated by 90°.
+
+Now our encoding processes such a permutation from beginning to end. It starts
+with an empty bin `1`. Each object is first placed with its right end at the
+right end of the bin and with its bottom line exactly at the top of the bin,
+i.e., outside of the bin. Then, in each step, we move the object as far down
+as possible. Then, we move it to the left as far as possible, but we
+immediately stop if there was another chance to move the object down. In
+other words, downward movements are preferred over left movements. This is
+repeated until no movement of the object is possible anymore.
+
+Once the object cannot be moved anymore, we check if it is fully inside the
+bin. If yes, then the object is included in the bin and we continue with the
+next object. If not, it does not fit into the bin.
+
+This is the "Improved Bottom Left" heuristic by Liu and Teng [1].
+
+If the object does not fit into the current bin, we place it at the
+bottom-left corner of a new bin. We therefore increase the bin counter.
+From now on, all the following objects will be placed into this bin until
+the bin is full as well, in which case we move to the next bin again.
+This means that the current bin is closed at the same moment the first
+object is encountered that does not fit into it anymore. Therefore,
+the objects in a closed bin do no longer need to be considered when packing
+subsequent objects.
+
+This is different from the second variant of this encoding implemented in file
+:mod:`moptipyapps.binpacking2d.encodings.ibl_encoding_2`, which always checks
+all the bins, starting at bin `1`, when placing any object. That other
+encoding variant therefore must always consider all bins and is thus slower,
+but tends to yield better packings.
+
+This procedure has originally been developed and implemented by Mr. Rui ZHAO
+(赵睿), <zr1329142665@163.com> a Master's student at the Institute of Applied
+Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of
+Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University
+(合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of
+Prof. Dr. Thomas Weise (汤卫思教授).
+
+1. Dequan Liu and Hongfei Teng. An Improved BL-Algorithm for Genetic Algorithm
+   of the Orthogonal Packing of Rectangles. European Journal of Operational
+   Research. 112(2):413-420. January (1999).
+   https://doi.org/10.1016/S0377-2217(97)00437-2.
+   http://www.paper.edu.cn/scholar/showpdf/MUT2AN0IOTD0Mxxh.
+"""
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.api.encoding import Encoding
+from moptipy.utils.logger import KeyValueLogSection
+from pycommons.types import type_error
+
+from moptipyapps.binpacking2d.instance import (
+    IDX_HEIGHT,
+    IDX_WIDTH,
+    Instance,
+)
+from moptipyapps.binpacking2d.packing import (
+    IDX_BIN,
+    IDX_BOTTOM_Y,
+    IDX_ID,
+    IDX_LEFT_X,
+    IDX_RIGHT_X,
+    IDX_TOP_Y,
+    Packing,
+)
+from moptipyapps.shared import SCOPE_INSTANCE
+
+
+@numba.njit(nogil=True, cache=True, inline="always", boundscheck=False)
+def __move_down(packing: np.ndarray, bin_start: int, i1: int) -> bool:
+    """
+    Move the box at index `i1` down as far as possible in the current bin.
+
+    `bin_start` is the index of the first object that has already been placed
+    in the current bin. It always holds that `i1 >= bin_start`. In the case
+    that `i1 == bin_start`, then we can move the object directly to the bottom
+    of the bin without any issue.
+
+    If `i1 > bin_start` we iterate over all objects at indices
+    `bin_start...i1-1`. We first set `min_down` to the bottom-y coordinate of
+    the box, because this is how far down we can move at most. Then, for each
+    of the objects already placed in the bin, we check if there is any
+    intersection of the horizontal with the current box. If there is no
+    intersection *or* if the object is already above the current box, then the
+    object will not influence the downward movement of our object. If there is
+    an intersection, then we cannot move the current box deeper than the top-y
+    coordinate of the other box.
+
+    *Only* the box at index `i1` is modified and if it is modified, this
+    function will return `True`.
+
+    :param packing: the packing under construction
+    :param bin_start: the starting index of the current bin
+    :param i1: the index of the current box
+    :return: `True` if the object was moved down, `False` if the object cannot
+        be moved down any further because either it has reached the bottom or
+        because it would intersect with other objects
+
+    >>> # itemID, binID, left-x, bottom-y, right-x, top-y
+    >>> r = np.array([[1, 1, 10, 20, 30, 40],
+    ...               [2, 1, 30, 30, 50, 60],
+    ...               [3, 1, 40, 100, 60, 200]])
+    >>> __move_down(r, 0, 2)  # try to move down the box at index 2
+    True
+    >>> print(r[2, :])
+    [  3   1  40  60  60 160]
+    >>> __move_down(r, 0, 2)  # try to move down the box at index 2 again
+    False
+    >>> __move_down(r, 0, 1)  # try to move down the box at index 1 (ignore 2)
+    True
+    >>> print(r[1, :])
+    [ 2  1 30  0 50 30]
+    >>> __move_down(r, 0, 1)  # try to move down the box at index 1 again
+    False
+    >>> __move_down(r, 0, 2)  # try to move down the box at index 2 again now
+    True
+    >>> print(r[2, :])
+    [  3   1  40  30  60 130]
+    >>> __move_down(r, 0, 2)  # try to move down the box at index 2 again
+    False
+    >>> __move_down(r, 0, 0)
+    True
+    >>> print(r[0, :])
+    [ 1  1 10  0 30 20]
+    >>> __move_down(r, 0, 0)
+    False
+    """
+    # load the coordinates of i1 into local variables to speed up computation
+    packing_i1_left_x: Final[int] = int(packing[i1, IDX_LEFT_X])
+    packing_i1_bottom_y: Final[int] = int(packing[i1, IDX_BOTTOM_Y])
+    packing_i1_right_x: Final[int] = int(packing[i1, IDX_RIGHT_X])
+    packing_i1_top_y: Final[int] = int(packing[i1, IDX_TOP_Y])
+    min_down: int = packing_i1_bottom_y  # maximum move: down to bottom
+    for i0 in range(bin_start, i1):  # iterate over all boxes in current bin
+        # An intersection exists if the right-x of an existing box is larger
+        # than the left-x of the new box AND if the left-x of the existing box
+        # is less than the right-x of the new box.
+        # Only intersections matter and only with objects not above us.
+        if (packing[i0, IDX_RIGHT_X] > packing_i1_left_x) and \
+                (packing[i0, IDX_LEFT_X] < packing_i1_right_x) and \
+                (packing[i0, IDX_BOTTOM_Y] < packing_i1_top_y):
+            # The object would horizontally intersect with the current object
+            min_down = min(min_down, int(
+                packing_i1_bottom_y - packing[i0, IDX_TOP_Y]))
+    if min_down > 0:  # Can we move down? If yes, update box.
+        packing[i1, IDX_BOTTOM_Y] = packing_i1_bottom_y - min_down
+        packing[i1, IDX_TOP_Y] = packing_i1_top_y - min_down
+        return True
+    return False
+
+
+@numba.njit(nogil=True, cache=True, inline="always", boundscheck=False)
+def __move_left(packing: np.ndarray, bin_start: int, i1: int) -> bool:
+    """
+    Move the box at index `i1` left as far as possible in the current bin.
+
+    This function moves a box to the left without changing its vertical
+    position. It is slightly more tricky than the downwards moving function,
+    because in the improved bottom left heuristic, downward moves are
+    preferred compared to left moves. This means that the box needs to be
+    stopped when reaching the edge of a box on whose top it sits.
+
+    This function is to be called *after* `__move_down` and in an alternating
+    fashion.
+
+    *Only* the box at index `i1` is modified and if it is modified, this
+    function will return `True`.
+
+    :param packing: the packing under construction
+    :param bin_start: the starting index of the current bin
+    :param i1: the index of the current box
+    :return: `True` if the object was moved down, `False` if the object cannot
+        be moved down any further because either it has reached the bottom or
+        because it would intersect with other objects
+
+    >>> # itemID, binID, left-x, bottom-y, right-x, top-y
+    >>> r = np.array([[1, 1,  0,  0, 30, 10],
+    ...               [2, 1, 35,  0, 45, 30],
+    ...               [3, 1,  0, 10, 10, 20],
+    ...               [4, 1, 40, 30, 50, 40]])
+    >>> __move_left(r, 0, 3)
+    True
+    >>> print(r[3, :])
+    [ 4  1 25 30 35 40]
+    >>> __move_left(r, 0, 3)
+    True
+    >>> print(r[3, :])
+    [ 4  1  0 30 10 40]
+    >>> r[3, :] = [4, 1, 25, 10, 35, 20]
+    >>> __move_left(r, 0, 3)
+    True
+    >>> print(r[3, :])
+    [ 4  1 10 10 20 20]
+    >>> __move_left(r, 0, 3)
+    False
+    >>> # itemID, binID, left-x, bottom-y, right-x, top-y
+    >>> r = np.array([[1, 1,  0,  0, 10, 2],
+    ...               [2, 1, 10,  0, 20, 5],
+    ...               [3, 1,  8,  2, 10, 4]])
+    >>> __move_left(r, 0, 2)
+    True
+    >>> print(r[2, :])
+    [3 1 0 2 2 4]
+    """
+    packing_i1_left_x: Final[int] = int(packing[i1, IDX_LEFT_X])
+    packing_i1_bottom_y: Final[int] = int(packing[i1, IDX_BOTTOM_Y])
+    packing_i1_right_x: Final[int] = int(packing[i1, IDX_RIGHT_X])
+    packing_i1_top_y: Final[int] = int(packing[i1, IDX_TOP_Y])
+    min_left: int = packing_i1_left_x
+    for i0 in range(bin_start, i1):
+        if packing[i0, IDX_LEFT_X] >= packing_i1_right_x:
+            continue  # the object is already behind us, so it can be ignored
+        if (packing[i0, IDX_RIGHT_X] > packing_i1_left_x) \
+                and (packing[i0, IDX_LEFT_X] < packing_i1_right_x):
+            # we have a horizontal intersection with a box below
+            if packing[i0, IDX_TOP_Y] == packing_i1_bottom_y:
+                # only consider those the box *directly* below and move the
+                # right end of the new box to the left end of that box below
+                min_left = min(min_left, int(
+                    packing_i1_right_x - packing[i0, IDX_LEFT_X]))
+        elif (packing_i1_top_y > packing[i0, IDX_BOTTOM_Y]) \
+                and (packing_i1_bottom_y < packing[i0, IDX_TOP_Y]):
+            min_left = min(min_left, int(
+                packing_i1_left_x - packing[i0, IDX_RIGHT_X]))
+    if min_left > 0:
+        # move the box to the left
+        packing[i1, IDX_LEFT_X] = packing_i1_left_x - min_left
+        packing[i1, IDX_RIGHT_X] = packing_i1_right_x - min_left
+        return True
+    return False
+
+
+@numba.njit(nogil=True, cache=True, inline="always", boundscheck=False)
+def _decode(x: np.ndarray, y: np.ndarray, instance: np.ndarray,
+            bin_width: int, bin_height: int) -> int:
+    """
+    Decode a (signed) permutation to a packing.
+
+    The permutation is processed from the beginning to the end.
+    Each element identifies one object by its ID. If the ID is negative,
+    the object will be inserted rotated by 90°. If the ID is positive, the
+    object will be inserted as is.
+
+    The absolute value of the ID-1 will be used to look up the width and
+    height of the object in the `instance` data. If the object needs to be
+    rotated, width and height will be swapped.
+
+    Each object is, at the beginning, placed with its right side at the right
+    end of the bin. The bottom line of the object is initially put on top of
+    the bin, i.e., initially the object is outside of the bin.
+
+    Then, the object is iteratively moved downward as far as possible. Once it
+    reaches another object, we move it to the left until either its right side
+    reaches the left end of the object beneath it or until its left side
+    touches another object. Then we try to move the object down again, and so
+    on.
+
+    Once the object can no longer be moved down, we check if it is now fully
+    inside of the bin. If yes, then good, the object's bin index is set to the
+    ID of the current bin. If not, then we cannot place the object into this
+    bin. In this case, we increase the bin ID by one. The object is put into
+    a new and empty bin. We move it to the bottom-left corner of this bin. In
+    other words, the left side of the object touches the left side of the bin,
+    i.e., is `0`. The bottom-line of the object is also the bottom of the bin,
+    i.e., has coordinate `0` as well.
+
+    All objects that are placed from now on will go into this bin until the
+    bin is full. Then we move on to the next bin, and so on. In other words,
+    once a bin is full, we no longer consider it for receiving any further
+    objects.
+
+    :param x: a possibly signed permutation
+    :param y: the packing object
+    :param instance: the packing instance data
+    :param bin_width: the bin width
+    :param bin_height: the bin height
+    :returns: the number of bins
+
+    As example, we use a slightly modified version (we add more objects so we
+    get to see the use of a second bin) of Figure 2 of the Liu and Teng paper
+    "An Improved BL-Algorithm for Genetic Algorithm of the Orthogonal Packing
+    of Rectangles."
+
+    >>> # [width, height, repetitions]
+    >>> inst = np.array([[10, 20, 5], [5, 5, 5]])
+    >>> # [id = plain, -id = rotated]
+    >>> xx = np.array([1, -1, 2, -2, 1, -2, -2, -1, -1, 2])
+    >>> # [id, bin, left-x, bottom-y, right-x, top-y] ...
+    >>> yy = np.empty((10, 6), int)
+    >>> print(_decode(xx, yy, inst, 30, 30))
+    2
+    >>> print(yy[0, :])
+    [ 1  1  0  0 10 20]
+    >>> print(yy[1, :])
+    [ 1  1 10  0 30 10]
+    >>> print(yy[2, :])
+    [ 2  1 10 10 15 15]
+    >>> print(yy[3, :])
+    [ 2  1 15 10 20 15]
+    >>> print(yy[4, :])
+    [ 1  1 20 10 30 30]
+    >>> print(yy[5, :])
+    [ 2  1 10 15 15 20]
+    >>> print(yy[6, :])
+    [ 2  1 15 15 20 20]
+    >>> print(yy[7, :])
+    [ 1  1  0 20 20 30]
+    >>> print(yy[8, :])
+    [ 1  2  0  0 20 10]
+    >>> print(yy[9, :])
+    [ 2  2 20  0 25  5]
+    """
+    w: int  # the width of the current object
+    h: int  # the height of the current object
+    use_id: int  # the id of the current object
+    bin_start: int = 0  # the index of the first object in the current bin
+    bin_id: int = 1  # the id of the current bin
+    for i, item_id in enumerate(x):  # iterate over all objects
+        if item_id < 0:  # object should be rotated
+            use_id = -(item_id + 1)  # get absolute id - 1
+            w = int(instance[use_id, IDX_HEIGHT])  # width = height (rotated!)
+            h = int(instance[use_id, IDX_WIDTH])   # height = width (rotated!)
+        else:  # the object will not be rotated
+            use_id = item_id - 1   # id - 1
+            w = int(instance[use_id, IDX_WIDTH])  # get width
+            h = int(instance[use_id, IDX_HEIGHT])  # get height
+
+# It could be that an object is too wide or too high for the bin in its
+# current rotation even if the bin was empty entirely. In this case, we simply
+# force-rotate it. A bin packing instance will not permit objects that do not
+# fit into the bin in any rotation. So if the object does not fit in its
+# current rotation, it must fit if we simply rotate it by 90°.
+        if (w > bin_width) or (h > bin_height):
+            w, h = h, w
+
+# At first, the object's right corner is at the right corner of the bin.
+# The object sits exactly at the top of the bin, i.e., its bottom line
+# is the top line of the bin.
+        y[i, IDX_ID] = use_id + 1  # the id of the object
+        y[i, IDX_LEFT_X] = bin_width - w  # the left end
+        y[i, IDX_BOTTOM_Y] = bin_height  # object sits on top of bin
+        y[i, IDX_RIGHT_X] = bin_width  # object ends at right end of bin
+        y[i, IDX_TOP_Y] = bin_height + h  # top of object is outside of bin
+
+        while __move_down(y, bin_start, i) or __move_left(y, bin_start, i):
+            pass  # loop until object can no longer be moved
+
+# If the object is not fully inside the current bin, we move to a new bin.
+        if (y[i, IDX_RIGHT_X] > bin_width) or (y[i, IDX_TOP_Y] > bin_height):
+            bin_id = bin_id + 1  # step to the next bin
+            bin_start = i  # set the starting index of the bin
+            y[i, IDX_LEFT_X] = 0  # the object goes to the left end of the bin
+            y[i, IDX_BOTTOM_Y] = 0  # the object goes to the bottom of the bin
+            y[i, IDX_RIGHT_X] = w  # so its right end is its width
+            y[i, IDX_TOP_Y] = h  # and its top end is its height
+        y[i, IDX_BIN] = bin_id  # store the bin id
+    return int(bin_id)  # return the total number of bins
+
+
+
+[docs] +class ImprovedBottomLeftEncoding1(Encoding): + """An Improved Bottem Left Encoding by Liu and Teng for multiple bins.""" + + def __init__(self, instance: Instance) -> None: + """ + Instantiate the improved best first encoding. + + :param instance: the packing instance + """ + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + #: the internal instance reference + self.__instance: Final[Instance] = instance + +
+[docs] + def decode(self, x: np.ndarray, y: Packing) -> None: + """ + Map a potentially signed permutation to a packing. + + :param x: the array + :param y: the Gantt chart + """ + y.n_bins = _decode(x, y, self.__instance, self.__instance.bin_width, + self.__instance.bin_height)
+ + + def __str__(self) -> str: + """ + Get the name of this encoding. + + :return: `"ibf1"` + :rtype: str + """ + return "ibf1" + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log all parameters of this component as key-value pairs. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + with logger.scope(SCOPE_INSTANCE) as kv: + self.__instance.log_parameters_to(kv)
+
+ +
diff --git a/_modules/moptipyapps/binpacking2d/encodings/ibl_encoding_2.html b/_modules/moptipyapps/binpacking2d/encodings/ibl_encoding_2.html new file mode 100644 index 00000000..137cead3 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/encodings/ibl_encoding_2.html @@ -0,0 +1,503 @@ +moptipyapps.binpacking2d.encodings.ibl_encoding_2 — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.encodings.ibl_encoding_2

+"""
+An improved bottom left encoding by Liu and Teng extended to multiple bins.
+
+Here we provide an implementation of the improved bottom left encoding by Liu
+and Teng [1], but extended to bins with limited height. If the height of the
+bin is a limiting factor, then our implementation will automatically use
+multiple bins. Another variant is given in
+:mod:`moptipyapps.binpacking2d.encodings.ibl_encoding_1`.
+
+An instance :mod:`~moptipyapps.binpacking2d.instance` of the
+two-dimensional bin packing problem defines a set of objects to be packed
+and a bin size (width and height). Each object to be packed has itself a
+width and a height as well as a repetition counter, which is `1` if the object
+only occurs a single time and larger otherwise (i.e., if the repetition
+counter is `5`, the object needs to be packaged five times).
+
+The encoding receives signed permutations with repetitions as input. Each
+element of the permutation identifies one object from the bin packing
+instance. Each such object ID must occur exactly as often as the repetition
+counter of the object in the instance data suggest. But the ID may also occur
+negated, in which case the object is supposed to rotated by 90°.
+
+Now our encoding processes such a permutation from beginning to end. It starts
+with an empty bin `1`. Each object is first placed with its right end at the
+right end of the first bin and with its bottom line exactly at the top of the
+bin, i.e., outside of the bin. Then, in each step, we move the object as far
+down as possible. Then, we move it to the left as far as possible, but we
+immediately stop if there was another chance to move the object down. In
+other words, downward movements are preferred over left movements. This is
+repeated until no movement of the object is possible anymore.
+
+Once the object cannot be moved anymore, we check if it is fully inside the
+bin. If yes, then the object is included in the bin and we continue with the
+next object. If not, it does not fit into the bin.
+
+This is the "Improved Bottom Left" heuristic by Liu and Teng [1].
+
+If the object does not fit into the first bin and we already "opened" a second
+bin, then we try to place it into the second bin using the same procedure. And
+then into the third bin if that does not work out, and so on. Until we have
+tried unsuccessfully all the bins that we have opened.
+
+In this case, we "open" the next bin and we place the object at the
+bottom-left corner of a new bin. Then we continue with the next object, again
+trying to put it into the first bin, then the second bin, and so on.
+
+This is different from the first variant of this encoding implemented in file
+:mod:`moptipyapps.binpacking2d.encodings.ibl_encoding_2`, which always and
+only tries to put objects into the last bin that was opened (and moves to a
+new bin if that does not work out). That variant of the encoding is therefore
+faster than the one here, but the one here tends to yield better packings.
+
+The problem with this is that we need to basically first try to put the object
+into the first bin. For this we need to look at *all* the objects that we have
+already placed, because it could be that we already have one object in the
+first bin, then one in the second bin that did not fit into the first bin,
+then one smaller object in the first bin again, and so on. If our new object
+does not fit into the first bin, then we need to do the same with the second
+bin, and so on. So for every bin we try, we need to look at all objects already
+placed. And the number of bins we could try could be equal to the number of
+objects that we have already placed (if each object occupies one bin alone).
+So we have a worst case complexity of O(n ** 2) for placing one object. And we
+do this for all objects, so we would have a O(n ** 3) overall complexity.
+Well, actually, it is worse: Because we repeat the process of looking at all
+the objects several times while moving our new item to the left and down and
+to the left and down. So I suspect that we actually have O(n ** 4).
+That is annoying.
+
+We try to alleviate this a little bit by remembering, for each bin, the index
+of the first object that we put in there and the index of the last object we
+put in there. Now within these two indices, there also might be objects that
+we placed into other bins. But for a very little overhead (remembering two
+values per bin), we have a certain chance to speed up the process in several
+situations. For instance, the worst case from above, that each object occupies
+exactly one bin by itself becomes easier because we would only look at one
+already placed object per bin.
+
+This procedure has originally been developed and implemented by Mr. Rui ZHAO
+(赵睿), <zr1329142665@163.com>, a Master's student at the Institute of Applied
+Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of
+Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University
+(合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of
+Prof. Dr. Thomas Weise (汤卫思教授).
+
+1. Dequan Liu and Hongfei Teng. An Improved BL-Algorithm for Genetic Algorithm
+   of the Orthogonal Packing of Rectangles. European Journal of Operational
+   Research. 112(2):413-420. January (1999).
+   https://doi.org/10.1016/S0377-2217(97)00437-2.
+   http://www.paper.edu.cn/scholar/showpdf/MUT2AN0IOTD0Mxxh.
+"""
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.api.encoding import Encoding
+from moptipy.utils.logger import KeyValueLogSection
+from pycommons.types import type_error
+
+from moptipyapps.binpacking2d.instance import (
+    IDX_HEIGHT,
+    IDX_WIDTH,
+    Instance,
+)
+from moptipyapps.binpacking2d.packing import (
+    IDX_BIN,
+    IDX_BOTTOM_Y,
+    IDX_ID,
+    IDX_LEFT_X,
+    IDX_RIGHT_X,
+    IDX_TOP_Y,
+    Packing,
+)
+from moptipyapps.shared import SCOPE_INSTANCE
+
+
+@numba.njit(nogil=True, cache=True, inline="always", boundscheck=False)
+def __move_down(packing: np.ndarray, bin_id: int,
+                bin_start: int, bin_end: int, i1: int) -> bool:
+    """
+    Move the box at index `i1` down as far as possible in the current bin.
+
+    `bin_start` is the index of the first object that has already been placed
+    in the current bin `bin_id`. It always holds that `i1 >= bin_start`. In
+    the case that `i1 == bin_start`, then we can move the object directly to
+    the bottom of the bin without any issue.
+    If `i1 > bin_start` we iterate over all objects at indices
+    `bin_start...bin_end - 1` whose bin equals `bin`. We first set `min_down`
+    to the bottom-y coordinate of the box, because this is how far down we can
+    move at most.
+
+    Then, for each of the objects already placed in the bin `bin_id`, we check
+    if there is any intersection of the horizontal with the current box. If
+    there is no intersection *or* if the object is not already above the
+    current box, then the object will not influence the downward movement of
+    our object. If there is an intersection, then we cannot move the current
+    box deeper than the top-y coordinate of the other box.
+
+    *Only* the box at index `i1` is modified and if it is modified, this
+    function will return `True`.
+
+    :param packing: the packing under construction
+    :param bin_id: the bin ID
+    :param bin_start: the starting index of the current bin
+    :param bin_end: the exclusive end index of the current bin
+    :param i1: the index of the current box
+    :return: `True` if the object was moved down, `False` if the object cannot
+        be moved down any further because either it has reached the bottom or
+        because it would intersect with other objects
+
+    >>> # itemID, binID, left-x, bottom-y, right-x, top-y
+    >>> r = np.array([[1, 1, 10, 20, 30, 40],
+    ...               [2, 1, 30, 30, 50, 60],
+    ...               [3, 1, 40, 100, 60, 200]])
+    >>> __move_down(r, 1, 0, 2, 2)  # try to move down the box at index 2
+    True
+    >>> print(r[2, :])
+    [  3   1  40  60  60 160]
+    >>> __move_down(r, 1, 0, 2, 2)  # try to move down the box at index 2
+    False
+    >>> __move_down(r, 1, 0, 1, 1)  # try to move down the box at index 1
+    True
+    >>> print(r[1, :])
+    [ 2  1 30  0 50 30]
+    >>> __move_down(r, 1, 0, 1, 1)  # try to move down the box at index 1
+    False
+    >>> __move_down(r, 1, 0, 2, 2)  # try to move down the box at index 2
+    True
+    >>> print(r[2, :])
+    [  3   1  40  30  60 130]
+    >>> __move_down(r, 1, 0, 2, 2)  # try to move down the box at index 2
+    False
+    >>> __move_down(r, 1, 0, 0, 0)
+    True
+    >>> print(r[0, :])
+    [ 1  1 10  0 30 20]
+    >>> __move_down(r, 1, 0, 0, 0)
+    False
+    """
+    # load the coordinates of i1 into local variables to speed up computation
+    packing_i1_left_x: Final[int] = int(packing[i1, IDX_LEFT_X])
+    packing_i1_bottom_y: Final[int] = int(packing[i1, IDX_BOTTOM_Y])
+    packing_i1_right_x: Final[int] = int(packing[i1, IDX_RIGHT_X])
+    packing_i1_top_y: Final[int] = int(packing[i1, IDX_TOP_Y])
+    min_down: int = packing_i1_bottom_y  # maximum move: down to bottom
+    for i0 in range(bin_start, bin_end):  # iterate over items in current bin
+        # An intersection exists if the right-x of an existing box is larger
+        # than the left-x of the new box AND if the left-x of the existing box
+        # is less than the right-x of the new box.
+        # Only intersections matter and *only* with objects in the same bin
+        # and only if the existing box is not already above the new box.
+        if (packing[i0, IDX_BIN] == bin_id) and \
+                (packing[i0, IDX_RIGHT_X] > packing_i1_left_x) and \
+                (packing[i0, IDX_LEFT_X] < packing_i1_right_x) and \
+                (packing[i0, IDX_BOTTOM_Y] < packing_i1_top_y):
+            # The object would horizontally intersect with the current object
+            min_down = min(min_down, int(
+                packing_i1_bottom_y - packing[i0, IDX_TOP_Y]))
+    if min_down > 0:  # Can we move down? If yes, update box.
+        packing[i1, IDX_BOTTOM_Y] = packing_i1_bottom_y - min_down
+        packing[i1, IDX_TOP_Y] = packing_i1_top_y - min_down
+        return True
+    return False
+
+
+@numba.njit(nogil=True, cache=True, inline="always", boundscheck=False)
+def __move_left(packing: np.ndarray, bin_id: int,
+                bin_start: int, bin_end: int, i1: int) -> bool:
+    """
+    Move the box at index `i1` left as far as possible in the current bin.
+
+    This function moves a box to the left without changing its vertical
+    position. It is slightly more tricky than the downwards moving function,
+    because in the improved bottom left heuristic, downward moves are
+    preferred compared to left moves. This means that the box needs to be
+    stopped when reaching the edge of a box on whose top it sits. Of course,
+    we only consider other objects inside the current bin
+    (with ID `bin_start`).
+
+    This function is to be called *after* `__move_down` and in an alternating
+    fashion.
+
+    *Only* the box at index `i1` is modified and if it is modified, this
+    function will return `True`.
+
+    :param packing: the packing under construction
+    :param bin_id: the bin ID
+    :param bin_start: the starting index of the current bin
+    :param bin_end: the exclusive end index of the current bin
+    :param i1: the index of the current box
+    :return: `True` if the object was moved down, `False` if the object cannot
+        be moved down any further because either it has reached the bottom or
+        because it would intersect with other objects
+
+    >>> # itemID, binID, left-x, bottom-y, right-x, top-y
+    >>> r = np.array([[1, 1,  0,  0, 30, 10],
+    ...               [2, 1, 35,  0, 45, 30],
+    ...               [3, 1,  0, 10, 10, 20],
+    ...               [4, 1, 40, 30, 50, 40]])
+    >>> __move_left(r, 1, 0, 3, 3)
+    True
+    >>> print(r[3, :])
+    [ 4  1 25 30 35 40]
+    >>> __move_left(r, 1, 0, 3, 3)
+    True
+    >>> print(r[3, :])
+    [ 4  1  0 30 10 40]
+    >>> r[3, :] = [4, 1, 25, 10, 35, 20]
+    >>> __move_left(r, 1, 0, 3, 3)
+    True
+    >>> print(r[3, :])
+    [ 4  1 10 10 20 20]
+    >>> __move_left(r, 1, 0, 3, 3)
+    False
+    >>> # itemID, binID, left-x, bottom-y, right-x, top-y
+    >>> r = np.array([[1, 1,  0,  0, 10, 2],
+    ...               [2, 1, 10,  0, 20, 5],
+    ...               [3, 1,  8,  2, 10, 4]])
+    >>> __move_left(r, 0, 0, 3, 2)
+    True
+    >>> print(r[2, :])
+    [3 1 0 2 2 4]
+    """
+    packing_i1_left_x: Final[int] = int(packing[i1, IDX_LEFT_X])
+    packing_i1_bottom_y: Final[int] = int(packing[i1, IDX_BOTTOM_Y])
+    packing_i1_right_x: Final[int] = int(packing[i1, IDX_RIGHT_X])
+    packing_i1_top_y: Final[int] = int(packing[i1, IDX_TOP_Y])
+    min_left: int = packing_i1_left_x
+    for i0 in range(bin_start, bin_end):
+        # consider only objects that are not yet behind us and are in
+        # the same bin may intersect
+        if (packing[i0, IDX_BIN] != bin_id) \
+                or (packing[i0, IDX_LEFT_X] >= packing_i1_right_x):
+            continue  # the object is already behind us, so it can be ignored
+        if (packing[i0, IDX_RIGHT_X] > packing_i1_left_x) \
+                and (packing[i0, IDX_LEFT_X] < packing_i1_right_x):
+            # we have a horizontal intersection with a box below
+            if packing[i0, IDX_TOP_Y] == packing_i1_bottom_y:
+                # only consider those the box *directly* below and move the
+                # right end of the new box to the left end of that box below
+                min_left = min(min_left, int(
+                    packing_i1_right_x - packing[i0, IDX_LEFT_X]))
+        elif (packing_i1_top_y > packing[i0, IDX_BOTTOM_Y]) \
+                and (packing_i1_bottom_y < packing[i0, IDX_TOP_Y]):
+            min_left = min(min_left, int(
+                packing_i1_left_x - packing[i0, IDX_RIGHT_X]))
+    if min_left > 0:
+        # move the box to the left
+        packing[i1, IDX_LEFT_X] = packing_i1_left_x - min_left
+        packing[i1, IDX_RIGHT_X] = packing_i1_right_x - min_left
+        return True
+    return False
+
+
+@numba.njit(nogil=True, cache=True, inline="always", boundscheck=False)
+def _decode(x: np.ndarray, y: np.ndarray, instance: np.ndarray,
+            bin_width: int, bin_height: int, bin_starts: np.ndarray,
+            bin_ends: np.ndarray) -> int:
+    """
+    Decode a (signed) permutation to a packing.
+
+    The permutation is processed from the beginning to the end.
+    Each element identifies one object by its ID. If the ID is negative,
+    the object will be inserted rotated by 90°. If the ID is positive, the
+    object will be inserted as is.
+
+    The absolute value of the ID-1 will be used to look up the width and
+    height of the object in the `instance` data. If the object needs to be
+    rotated, width and height will be swapped.
+
+    Each object is, at the beginning, placed with its right side at the right
+    end of the *first* bin. The bottom line of the object is initially put on
+    top of the bin, i.e., initially the object is outside of the bin.
+
+    Then, the object is iteratively moved downward *in the bin* as far as
+    possible. Once it reaches another object, we move it to the left until
+    either its right side reaches the left end of the object beneath it or
+    until its left side touches another object. Then we try to move the object
+    down again, and so on.
+
+    Once the object can no longer be moved down, we check if it is now fully
+    inside of *the bin*. If yes, then good, the object's bin index is set to
+    the ID of the current bin. If not, then we cannot place the object into
+    this bin. In this case, we move to the second bin an repeat the process.
+    If the object fits into the second bin, then it is placed in there and
+    we are finished. If not, we try the third bin, and so on.
+
+    If we cannot fit the object into any of the bins that already have other
+    objects, then we allocate a new empty bin. We move the object to the
+    bottom-left corner of this bin. In other words, the left side of the
+    object touches the left side of the bin, i.e., is `0`. The bottom-line
+    of the object is also the bottom of the bin, i.e., has coordinate `0` as
+    well.
+
+    :param x: a possibly signed permutation
+    :param y: the packing object
+    :param instance: the packing instance data
+    :param bin_width: the bin width
+    :param bin_height: the bin height
+    :param bin_starts: a temporary index holder array for bin starts
+    :param bin_ends: a temporary index holder array for bin ends
+    :returns: the number of bins
+
+    As example, we use a slightly modified version (we add more objects so we
+    get to see the use of a second bin) of Figure 2 of the Liu and Teng paper
+    "An Improved BL-Algorithm for Genetic Algorithm of the Orthogonal Packing
+    of Rectangles."
+
+    >>> # [width, height, repetitions]
+    >>> inst = np.array([[10, 20, 5], [5, 5, 5]])
+    >>> # [id = plain, -id = rotated]
+    >>> xx = np.array([1, -1, 2, -2, 1, -2, -2, -1, -1, 2])
+    >>> # [id, bin, left-x, bottom-y, right-x, top-y] ...
+    >>> yy = np.empty((10, 6), int)
+    >>> binstarts = np.empty(10, int)
+    >>> binends = np.empty(10, int)
+    >>> print(_decode(xx, yy, inst, 30, 30, binstarts, binends))
+    2
+    >>> print(yy[0, :])
+    [ 1  1  0  0 10 20]
+    >>> print(yy[1, :])
+    [ 1  1 10  0 30 10]
+    >>> print(yy[2, :])
+    [ 2  1 10 10 15 15]
+    >>> print(yy[3, :])
+    [ 2  1 15 10 20 15]
+    >>> print(yy[4, :])
+    [ 1  1 20 10 30 30]
+    >>> print(yy[5, :])
+    [ 2  1 10 15 15 20]
+    >>> print(yy[6, :])
+    [ 2  1 15 15 20 20]
+    >>> print(yy[7, :])
+    [ 1  1  0 20 20 30]
+    >>> print(yy[8, :])
+    [ 1  2  0  0 20 10]
+    >>> print(yy[9, :])
+    [ 2  2 20  0 25  5]
+    """
+    w: int  # the width of the current object
+    h: int  # the height of the current object
+    use_id: int  # the id of the current object
+    bin_id: int = 1  # the id of the current bin
+    bin_starts[0] = 0
+    bin_ends[0] = 0
+    for i, item_id in enumerate(x):  # iterate over all objects
+        if item_id < 0:  # object should be rotated
+            use_id = -(item_id + 1)  # get absolute id - 1
+            w = int(instance[use_id, IDX_HEIGHT])  # width = height (rotated!)
+            h = int(instance[use_id, IDX_WIDTH])   # height = width (rotated!)
+        else:  # the object will not be rotated
+            use_id = item_id - 1   # id - 1
+            w = int(instance[use_id, IDX_WIDTH])  # get width
+            h = int(instance[use_id, IDX_HEIGHT])  # get height
+
+# It could be that an object is too wide or too high for the bin in its
+# current rotation even if the bin was empty entirely. In this case, we simply
+# force-rotate it. A bin packing instance will not permit objects that do not
+# fit into the bin in any rotation. So if the object does not fit in its
+# current rotation, it must fit if we simply rotate it by 90°.
+        if (w > bin_width) or (h > bin_height):
+            w, h = h, w
+
+# At first, the object's right corner is at the right corner of the bin.
+# The object sits exactly at the top of the bin, i.e., its bottom line
+# is the top line of the bin.
+        y[i, IDX_ID] = use_id + 1  # the id of the object
+
+        not_found: bool = True
+        for item_bin in range(1, bin_id + 1):  # iterate over all bins in use
+            bin_start = bin_starts[item_bin - 1]  # index of first item in bin
+            bin_end = bin_ends[item_bin - 1]  # index after last item in bin
+            y[i, IDX_LEFT_X] = bin_width - w  # the left end
+            y[i, IDX_BOTTOM_Y] = bin_height  # object sits on top of bin
+            y[i, IDX_RIGHT_X] = bin_width  # object ends at right end of bin
+            y[i, IDX_TOP_Y] = bin_height + h  # top of object is outside of bin
+
+            while __move_down(y, item_bin, int(bin_start), int(bin_end), i) \
+                    or __move_left(y, item_bin, int(bin_start),
+                                   int(bin_end), i):
+                pass  # loop until object can no longer be moved
+
+# Check if the object is completely inside the bin. If yes, we can put it
+# there and stop searching.
+            if (y[i, IDX_RIGHT_X] <= bin_width) \
+                    and (y[i, IDX_TOP_Y] <= bin_height):
+                not_found = False  # we found a placement, outer loop can stop
+                y[i, IDX_BIN] = item_bin  # set the items bin
+                bin_ends[item_bin - 1] = i + 1  # index after last item in bin
+                break
+
+# If the object is not fully inside the current bin, we move to a new bin.
+        if not_found:  # we did not find a spot in any of the bins
+            bin_starts[bin_id] = i  # set the starting index of the new bin
+            bin_ends[bin_id] = i + 1  # set the end index of the new bin
+            bin_id = bin_id + 1  # step to the next bin
+            y[i, IDX_LEFT_X] = 0  # the object goes to the left end of the bin
+            y[i, IDX_BOTTOM_Y] = 0  # the object goes to the bottom of the bin
+            y[i, IDX_RIGHT_X] = w  # so its right end is its width
+            y[i, IDX_TOP_Y] = h  # and its top end is its height
+            y[i, IDX_BIN] = bin_id  # store the bin id
+    return int(bin_id)  # return the total number of bins
+
+
+
+[docs] +class ImprovedBottomLeftEncoding2(Encoding): + """An Improved Bottem Left Encoding by Liu and Teng for multiple bins.""" + + def __init__(self, instance: Instance) -> None: + """ + Instantiate the improved best first encoding. + + :param instance: the packing instance + """ + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + #: the internal instance reference + self.__instance: Final[Instance] = instance + #: the temporary array for holding bin start indexes + self.__bin_starts: Final[np.ndarray] = np.empty( + instance.n_items, instance.dtype) + #: the temporary array for holding bin end indexes + self.__bin_ends: Final[np.ndarray] = np.empty( + instance.n_items, instance.dtype) + +
+[docs] + def decode(self, x: np.ndarray, y: Packing) -> None: + """ + Map a potentially signed permutation to a packing. + + :param x: the array + :param y: the Gantt chart + """ + y.n_bins = _decode(x, y, self.__instance, self.__instance.bin_width, + self.__instance.bin_height, self.__bin_starts, + self.__bin_ends)
+ + + def __str__(self) -> str: + """ + Get the name of this encoding. + + :return: `"ibf2"` + :rtype: str + """ + return "ibf2" + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log all parameters of this component as key-value pairs. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + with logger.scope(SCOPE_INSTANCE) as kv: + self.__instance.log_parameters_to(kv)
+
+ +
diff --git a/_modules/moptipyapps/binpacking2d/experiment.html b/_modules/moptipyapps/binpacking2d/experiment.html new file mode 100644 index 00000000..c82aef17 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/experiment.html @@ -0,0 +1,150 @@ +moptipyapps.binpacking2d.experiment — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.experiment

+"""An example experiment for bin packing."""
+
+import argparse
+from typing import Callable, Final, cast
+
+from moptipy.algorithms.so.ffa.fea1plus1 import FEA1plus1
+from moptipy.algorithms.so.rls import RLS
+from moptipy.api.encoding import Encoding
+from moptipy.api.execution import Execution
+from moptipy.api.experiment import run_experiment
+from moptipy.api.objective import Objective
+from moptipy.operators.signed_permutations.op0_shuffle_and_flip import (
+    Op0ShuffleAndFlip,
+)
+from moptipy.operators.signed_permutations.op1_swap_2_or_flip import (
+    Op1Swap2OrFlip,
+)
+from moptipy.spaces.signed_permutations import SignedPermutations
+from pycommons.io.path import Path
+
+from moptipyapps.binpacking2d.encodings.ibl_encoding_1 import (
+    ImprovedBottomLeftEncoding1,
+)
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.packing_result import DEFAULT_OBJECTIVES
+from moptipyapps.binpacking2d.packing_space import PackingSpace
+from moptipyapps.shared import moptipyapps_argparser
+
+#: the maximum number of FEs
+MAX_FES: Final[int] = 1_000_000
+
+
+
+[docs] +def base_setup(instance: Instance, + encoding: Callable[[Instance], Encoding], + objective: Callable[[Instance], Objective]) \ + -> tuple[SignedPermutations, Execution]: + """ + Create the basic setup. + + :param instance: the instance to use + :param encoding: the encoding function + :param objective: the objective function + :return: the search space and the basic execution + """ + space: Final[SignedPermutations] = SignedPermutations( + instance.get_standard_item_sequence()) + + return (space, Execution().set_max_fes(MAX_FES).set_log_improvements(True) + .set_objective(objective(instance)) + .set_encoding(encoding(instance)) + .set_search_space(space) + .set_solution_space(PackingSpace(instance)))
+ + + +
+[docs] +def rls(instance: Instance, + encoding: Callable[[Instance], Encoding], + objective: Callable[[Instance], Objective]) -> Execution: + """ + Create the RLS setup. + + :param instance: the instance to use + :param encoding: the encoding function + :param objective: the objective function + :return: the RLS execution + """ + space, execute = base_setup(instance, encoding, objective) + return execute.set_algorithm( + RLS(Op0ShuffleAndFlip(space), Op1Swap2OrFlip()))
+ + + +
+[docs] +def fea(instance: Instance, + encoding: Callable[[Instance], Encoding], + objective: Callable[[Instance], Objective]) -> Execution: + """ + Create the FEA setup. + + :param instance: the instance to use + :param encoding: the encoding function + :param objective: the objective function + :return: the RLS execution + """ + space, execute = base_setup(instance, encoding, objective) + return execute.set_algorithm( + FEA1plus1(Op0ShuffleAndFlip(space), Op1Swap2OrFlip()))
+ + + +
+[docs] +def run(base_dir: str, n_runs: int = 23) -> None: + """ + Run the experiment. + + :param base_dir: the base directory + :param n_runs: the number of runs, by default `23` + """ + use_dir: Final[Path] = Path(base_dir) + use_dir.ensure_dir_exists() + + encodings: Final[tuple[Callable[[Instance], Encoding], ...]] = ( + ImprovedBottomLeftEncoding1, + ) + instances: list[str] = [ + inst for inst in Instance.list_resources() + if inst.startswith(("b", "a"))] + inst_creators: list[Callable[[], Instance]] = [cast( + Callable[[], Instance], lambda __s=_s: Instance.from_resource(__s)) + for _s in instances] + namer: Final[Instance] = Instance.from_resource(instances[0]) + + for objective in DEFAULT_OBJECTIVES: + objective_dir: Path = use_dir.resolve_inside(str(objective(namer))) + objective_dir.ensure_dir_exists() + for encoding in encodings: + encoding_dir: Path = objective_dir.resolve_inside( + str(encoding(namer))) + encoding_dir.ensure_dir_exists() + run_experiment( + base_dir=encoding_dir, + instances=inst_creators, + setups=[ + cast(Callable, lambda ins, _e=encoding, _o=objective: rls( + ins, _e, _o)), + cast(Callable, lambda ins, _e=encoding, _o=objective: fea( + ins, _e, _o))], + n_runs=n_runs, + perform_warmup=True, + perform_pre_warmup=True)
+ + + +# Run the experiment from the command line +if __name__ == "__main__": + parser: Final[argparse.ArgumentParser] = moptipyapps_argparser( + __file__, "2D Bin Packing", "Run the 2D Bin Packing experiment.") + parser.add_argument( + "dest", help="the directory to store the experimental results under", + type=Path, nargs="?", default="./results/") + args: Final[argparse.Namespace] = parser.parse_args() + run(args.dest) +
diff --git a/_modules/moptipyapps/binpacking2d/instance.html b/_modules/moptipyapps/binpacking2d/instance.html new file mode 100644 index 00000000..fe410240 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/instance.html @@ -0,0 +1,967 @@ +moptipyapps.binpacking2d.instance — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.instance

+"""
+A Two-Dimensional Bin Packing instance.
+
+This module provides an instance of the two-dimensional bin packing problem
+as defined in 2DPackLib [1, 2] as well as the four non-trivial 'Almost Squares
+in Almost Squares' instances [6, 7].
+
+All instances of :class:`~moptipyapps.binpacking2d.instance.Instance`
+are two-dimensional numpy `ndarrays` with additional attributes.
+Each instance has a :attr:`~Instance.name`. Instances also specify a
+:attr:`~Instance.bin_width` and :attr:`~Instance.bin_height`.
+They define the number :attr:`~Instance.n_different_items` of items with
+*different* IDs. Notice that in the 2DPackLib dataset, a benchmark instance
+may contain the same item multiple times. Obviously, all items of the same
+ID have the exact same width and height, meaning that we only need to store
+them once and remember how often they occur. (Notice that the opposite is not
+true, i.e., not all items with the same width and height do have the same ID.)
+Anyway, the total number :attr:`~Instance.n_items` of items, i.e., the sum of
+all the repetitions of all items, is also stored.
+
+The matrix data of the instance class is laid out as follows: There is one row
+for each item. The row contains the width of the item, the height of the item,
+and the number of times the item will occur. The row at index `i` stands for
+the item with ID `i+1`.
+
+Instances can be loaded directly from a 2DPackLib file via
+:meth:`Instance.from_2dpacklib`. They can also be loaded from a compact string
+representation (via :meth:`Instance.from_compact_str`) and can also be
+converted to such a compact representation
+(via :meth:`Instance.to_compact_str`). This library ships with a set of
+pre-defined instances as resource which can be obtained via
+:meth:`Instance.from_resource` and listed via
+:meth:`Instance.list_resources`.
+
+We provide the instances of the sets `A` [3], `BENG` [4], and `CLASS` [5]
+from 2DPackLib. Additionally, we include the four non-trivial 'Almost Squares
+in Almost Squares' instances [6,7].
+
+Initial work on this code has been contributed by Mr. Rui ZHAO (赵睿),
+<zr1329142665@163.com> a Master's student at the Institute of Applied
+Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of
+Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University
+(合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of
+Prof. Dr. Thomas Weise (汤卫思教授).
+
+1. Manuel Iori, Vinícius Loti de Lima, Silvano Martello, and Michele Monaci.
+   *2DPackLib*.
+   https://site.unibo.it/operations-research/en/research/2dpacklib
+2. Manuel Iori, Vinícius Loti de Lima, Silvano Martello, and Michele
+   Monaci. 2DPackLib: A Two-Dimensional Cutting and Packing Library.
+   *Optimization Letters* 16(2):471-480. March 2022.
+   https://doi.org/10.1007/s11590-021-01808-y
+3. Rita Macedo, Cláudio Alves, and José M. Valério de Carvalho. Arc-Flow
+   Model for the Two-Dimensional Guillotine Cutting Stock Problem.
+   *Computers & Operations Research* 37(6):991-1001. June 2010.
+   https://doi.org/10.1016/j.cor.2009.08.005.
+4. Bengt-Erik Bengtsson. Packing Rectangular Pieces - A Heuristic Approach.
+   *The Computer Journal* 25(3):353-357, August 1982.
+   https://doi.org/10.1093/comjnl/25.3.353
+5. J.O. Berkey and P.Y. Wang. Two Dimensional Finite Bin Packing Algorithms.
+   *Journal of the Operational Research Society* 38(5):423-429. May 1987.
+   https://doi.org/10.1057/jors.1987.70
+6. Daan van den Berg, Florian Braam, Mark Moes, Emiel Suilen, and
+   Sandjai Bhulai. Almost Squares in Almost Squares: Solving the Final
+   Instance. In *The Fifth International Conference on Data Analytics
+   (DATA ANALYTICS'16)* October 9-13, 2016, Venice, Italy, pages 69-74.
+   IARIA. ISBN: 9781612085104. https://hdl.handle.net/11245/1.545914.
+   https://math.vu.nl/~sbhulai/publications/data_analytics2016b.pdf.
+7. H. Simonis and B. O'Sullivan. Almost Square Packing in *8th International
+   Conference on Integration of AI and OR Techniques in Constraint Programming
+   for Combinatorial Optimization Problems* (CPAIOR'11), May 23-27, 2011,
+   Berlin, Germany, pages 196-209. Berlin, Germany: Springer-Verlag.
+   doi:10.1007/978-3-642-21311-3_19.
+
+>>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]])
+>>> ins.name
+'a'
+>>> ins.bin_width
+100
+>>> ins.bin_height
+50
+>>> ins.dtype
+dtype('int8')
+>>> ins.n_different_items
+3
+>>> ins.to_compact_str()
+'a;3;100;50;10,5;3,3;5,5'
+>>> ins = Instance("b", 100, 50, np.array([[10, 5, 1], [3, 3, 1], [3, 3, 1]]))
+>>> ins.name
+'b'
+>>> ins.bin_width
+100
+>>> ins.bin_height
+50
+>>> ins.dtype
+dtype('int8')
+>>> ins.to_compact_str()
+'b;3;100;50;10,5;3,3;3,3'
+>>> ins = Instance.from_resource("cl02_020_06")
+>>> ins.dtype
+dtype('int8')
+>>> ins.n_different_items
+20
+>>> ins.n_items
+20
+>>> ins = Instance.from_resource("a25")
+>>> ins.dtype
+dtype('int16')
+>>> ins.n_different_items
+75
+>>> ins.n_items
+156
+"""
+
+from importlib import resources  # nosem
+from os.path import basename
+from statistics import quantiles
+from typing import Final, Iterable, cast
+
+import moptipy.utils.nputils as npu
+import numpy as np
+from moptipy.api.component import Component
+from moptipy.utils.logger import CSV_SEPARATOR, KeyValueLogSection
+from moptipy.utils.nputils import int_range_to_dtype
+from moptipy.utils.strings import sanitize_name
+from pycommons.io.path import UTF8, Path, file_path
+from pycommons.types import check_int_range, check_to_int_range, type_error
+
+#: the instances resource name
+INSTANCES_RESOURCE: Final[str] = "instances.txt"
+
+#: the total number of items
+N_ITEMS: Final[str] = "nItems"
+#: the number of different items.
+N_DIFFERENT_ITEMS: Final[str] = "nDifferentItems"
+#: the bin width
+BIN_WIDTH: Final[str] = "binWidth"
+#: the bin height
+BIN_HEIGHT: Final[str] = "binHeight"
+#: The internal item separator
+INTERNAL_SEP: Final[str] = "," if CSV_SEPARATOR == ";" else ";"
+
+#: the index of the width element in an item of an instance
+IDX_WIDTH: Final[int] = 0
+#: the index of the height element in an item of an instance
+IDX_HEIGHT: Final[int] = 1
+#: the index of the repetitions element in an item of an instance
+IDX_REPETITION: Final[int] = 2
+
+#: the list of instance names of the 2DPackLib bin packing set downloaded
+#: from https://site.unibo.it/operations-research/en/research/2dpacklib
+#: ('a*','beng*', 'cl*') as well as the four non-trivial 'Almost Squares in
+#: Almost Squares' instances ('asqas*').
+_INSTANCES: Final[tuple[str, ...]] = (
+    "a01", "a02", "a03", "a04", "a05", "a06", "a07", "a08", "a09", "a10",
+    "a11", "a12", "a13", "a14", "a15", "a16", "a17", "a18", "a19", "a20",
+    "a21", "a22", "a23", "a24", "a25", "a26", "a27", "a28", "a29", "a30",
+    "a31", "a32", "a33", "a34", "a35", "a36", "a37", "a38", "a39", "a40",
+    "a41", "a42", "a43", "asqas03", "asqas08", "asqas20", "asqas34", "beng01",
+    "beng02", "beng03", "beng04", "beng05", "beng06", "beng07", "beng08",
+    "beng09", "beng10", "cl01_020_01", "cl01_020_02", "cl01_020_03",
+    "cl01_020_04", "cl01_020_05", "cl01_020_06", "cl01_020_07", "cl01_020_08",
+    "cl01_020_09", "cl01_020_10", "cl01_040_01", "cl01_040_02", "cl01_040_03",
+    "cl01_040_04", "cl01_040_05", "cl01_040_06", "cl01_040_07", "cl01_040_08",
+    "cl01_040_09", "cl01_040_10", "cl01_060_01", "cl01_060_02", "cl01_060_03",
+    "cl01_060_04", "cl01_060_05", "cl01_060_06", "cl01_060_07", "cl01_060_08",
+    "cl01_060_09", "cl01_060_10", "cl01_080_01", "cl01_080_02", "cl01_080_03",
+    "cl01_080_04", "cl01_080_05", "cl01_080_06", "cl01_080_07", "cl01_080_08",
+    "cl01_080_09", "cl01_080_10", "cl01_100_01", "cl01_100_02", "cl01_100_03",
+    "cl01_100_04", "cl01_100_05", "cl01_100_06", "cl01_100_07", "cl01_100_08",
+    "cl01_100_09", "cl01_100_10", "cl02_020_01", "cl02_020_02", "cl02_020_03",
+    "cl02_020_04", "cl02_020_05", "cl02_020_06", "cl02_020_07", "cl02_020_08",
+    "cl02_020_09", "cl02_020_10", "cl02_040_01", "cl02_040_02", "cl02_040_03",
+    "cl02_040_04", "cl02_040_05", "cl02_040_06", "cl02_040_07", "cl02_040_08",
+    "cl02_040_09", "cl02_040_10", "cl02_060_01", "cl02_060_02", "cl02_060_03",
+    "cl02_060_04", "cl02_060_05", "cl02_060_06", "cl02_060_07", "cl02_060_08",
+    "cl02_060_09", "cl02_060_10", "cl02_080_01", "cl02_080_02", "cl02_080_03",
+    "cl02_080_04", "cl02_080_05", "cl02_080_06", "cl02_080_07", "cl02_080_08",
+    "cl02_080_09", "cl02_080_10", "cl02_100_01", "cl02_100_02", "cl02_100_03",
+    "cl02_100_04", "cl02_100_05", "cl02_100_06", "cl02_100_07", "cl02_100_08",
+    "cl02_100_09", "cl02_100_10", "cl03_020_01", "cl03_020_02", "cl03_020_03",
+    "cl03_020_04", "cl03_020_05", "cl03_020_06", "cl03_020_07", "cl03_020_08",
+    "cl03_020_09", "cl03_020_10", "cl03_040_01", "cl03_040_02", "cl03_040_03",
+    "cl03_040_04", "cl03_040_05", "cl03_040_06", "cl03_040_07", "cl03_040_08",
+    "cl03_040_09", "cl03_040_10", "cl03_060_01", "cl03_060_02", "cl03_060_03",
+    "cl03_060_04", "cl03_060_05", "cl03_060_06", "cl03_060_07", "cl03_060_08",
+    "cl03_060_09", "cl03_060_10", "cl03_080_01", "cl03_080_02", "cl03_080_03",
+    "cl03_080_04", "cl03_080_05", "cl03_080_06", "cl03_080_07", "cl03_080_08",
+    "cl03_080_09", "cl03_080_10", "cl03_100_01", "cl03_100_02", "cl03_100_03",
+    "cl03_100_04", "cl03_100_05", "cl03_100_06", "cl03_100_07", "cl03_100_08",
+    "cl03_100_09", "cl03_100_10", "cl04_020_01", "cl04_020_02", "cl04_020_03",
+    "cl04_020_04", "cl04_020_05", "cl04_020_06", "cl04_020_07", "cl04_020_08",
+    "cl04_020_09", "cl04_020_10", "cl04_040_01", "cl04_040_02", "cl04_040_03",
+    "cl04_040_04", "cl04_040_05", "cl04_040_06", "cl04_040_07", "cl04_040_08",
+    "cl04_040_09", "cl04_040_10", "cl04_060_01", "cl04_060_02", "cl04_060_03",
+    "cl04_060_04", "cl04_060_05", "cl04_060_06", "cl04_060_07", "cl04_060_08",
+    "cl04_060_09", "cl04_060_10", "cl04_080_01", "cl04_080_02", "cl04_080_03",
+    "cl04_080_04", "cl04_080_05", "cl04_080_06", "cl04_080_07", "cl04_080_08",
+    "cl04_080_09", "cl04_080_10", "cl04_100_01", "cl04_100_02", "cl04_100_03",
+    "cl04_100_04", "cl04_100_05", "cl04_100_06", "cl04_100_07", "cl04_100_08",
+    "cl04_100_09", "cl04_100_10", "cl05_020_01", "cl05_020_02", "cl05_020_03",
+    "cl05_020_04", "cl05_020_05", "cl05_020_06", "cl05_020_07", "cl05_020_08",
+    "cl05_020_09", "cl05_020_10", "cl05_040_01", "cl05_040_02", "cl05_040_03",
+    "cl05_040_04", "cl05_040_05", "cl05_040_06", "cl05_040_07", "cl05_040_08",
+    "cl05_040_09", "cl05_040_10", "cl05_060_01", "cl05_060_02", "cl05_060_03",
+    "cl05_060_04", "cl05_060_05", "cl05_060_06", "cl05_060_07", "cl05_060_08",
+    "cl05_060_09", "cl05_060_10", "cl05_080_01", "cl05_080_02", "cl05_080_03",
+    "cl05_080_04", "cl05_080_05", "cl05_080_06", "cl05_080_07", "cl05_080_08",
+    "cl05_080_09", "cl05_080_10", "cl05_100_01", "cl05_100_02", "cl05_100_03",
+    "cl05_100_04", "cl05_100_05", "cl05_100_06", "cl05_100_07", "cl05_100_08",
+    "cl05_100_09", "cl05_100_10", "cl06_020_01", "cl06_020_02", "cl06_020_03",
+    "cl06_020_04", "cl06_020_05", "cl06_020_06", "cl06_020_07", "cl06_020_08",
+    "cl06_020_09", "cl06_020_10", "cl06_040_01", "cl06_040_02", "cl06_040_03",
+    "cl06_040_04", "cl06_040_05", "cl06_040_06", "cl06_040_07", "cl06_040_08",
+    "cl06_040_09", "cl06_040_10", "cl06_060_01", "cl06_060_02", "cl06_060_03",
+    "cl06_060_04", "cl06_060_05", "cl06_060_06", "cl06_060_07", "cl06_060_08",
+    "cl06_060_09", "cl06_060_10", "cl06_080_01", "cl06_080_02", "cl06_080_03",
+    "cl06_080_04", "cl06_080_05", "cl06_080_06", "cl06_080_07", "cl06_080_08",
+    "cl06_080_09", "cl06_080_10", "cl06_100_01", "cl06_100_02", "cl06_100_03",
+    "cl06_100_04", "cl06_100_05", "cl06_100_06", "cl06_100_07", "cl06_100_08",
+    "cl06_100_09", "cl06_100_10", "cl07_020_01", "cl07_020_02", "cl07_020_03",
+    "cl07_020_04", "cl07_020_05", "cl07_020_06", "cl07_020_07", "cl07_020_08",
+    "cl07_020_09", "cl07_020_10", "cl07_040_01", "cl07_040_02", "cl07_040_03",
+    "cl07_040_04", "cl07_040_05", "cl07_040_06", "cl07_040_07", "cl07_040_08",
+    "cl07_040_09", "cl07_040_10", "cl07_060_01", "cl07_060_02", "cl07_060_03",
+    "cl07_060_04", "cl07_060_05", "cl07_060_06", "cl07_060_07", "cl07_060_08",
+    "cl07_060_09", "cl07_060_10", "cl07_080_01", "cl07_080_02", "cl07_080_03",
+    "cl07_080_04", "cl07_080_05", "cl07_080_06", "cl07_080_07", "cl07_080_08",
+    "cl07_080_09", "cl07_080_10", "cl07_100_01", "cl07_100_02", "cl07_100_03",
+    "cl07_100_04", "cl07_100_05", "cl07_100_06", "cl07_100_07", "cl07_100_08",
+    "cl07_100_09", "cl07_100_10", "cl08_020_01", "cl08_020_02", "cl08_020_03",
+    "cl08_020_04", "cl08_020_05", "cl08_020_06", "cl08_020_07", "cl08_020_08",
+    "cl08_020_09", "cl08_020_10", "cl08_040_01", "cl08_040_02", "cl08_040_03",
+    "cl08_040_04", "cl08_040_05", "cl08_040_06", "cl08_040_07", "cl08_040_08",
+    "cl08_040_09", "cl08_040_10", "cl08_060_01", "cl08_060_02", "cl08_060_03",
+    "cl08_060_04", "cl08_060_05", "cl08_060_06", "cl08_060_07", "cl08_060_08",
+    "cl08_060_09", "cl08_060_10", "cl08_080_01", "cl08_080_02", "cl08_080_03",
+    "cl08_080_04", "cl08_080_05", "cl08_080_06", "cl08_080_07", "cl08_080_08",
+    "cl08_080_09", "cl08_080_10", "cl08_100_01", "cl08_100_02", "cl08_100_03",
+    "cl08_100_04", "cl08_100_05", "cl08_100_06", "cl08_100_07", "cl08_100_08",
+    "cl08_100_09", "cl08_100_10", "cl09_020_01", "cl09_020_02", "cl09_020_03",
+    "cl09_020_04", "cl09_020_05", "cl09_020_06", "cl09_020_07", "cl09_020_08",
+    "cl09_020_09", "cl09_020_10", "cl09_040_01", "cl09_040_02", "cl09_040_03",
+    "cl09_040_04", "cl09_040_05", "cl09_040_06", "cl09_040_07", "cl09_040_08",
+    "cl09_040_09", "cl09_040_10", "cl09_060_01", "cl09_060_02", "cl09_060_03",
+    "cl09_060_04", "cl09_060_05", "cl09_060_06", "cl09_060_07", "cl09_060_08",
+    "cl09_060_09", "cl09_060_10", "cl09_080_01", "cl09_080_02", "cl09_080_03",
+    "cl09_080_04", "cl09_080_05", "cl09_080_06", "cl09_080_07", "cl09_080_08",
+    "cl09_080_09", "cl09_080_10", "cl09_100_01", "cl09_100_02", "cl09_100_03",
+    "cl09_100_04", "cl09_100_05", "cl09_100_06", "cl09_100_07", "cl09_100_08",
+    "cl09_100_09", "cl09_100_10", "cl10_020_01", "cl10_020_02", "cl10_020_03",
+    "cl10_020_04", "cl10_020_05", "cl10_020_06", "cl10_020_07", "cl10_020_08",
+    "cl10_020_09", "cl10_020_10", "cl10_040_01", "cl10_040_02", "cl10_040_03",
+    "cl10_040_04", "cl10_040_05", "cl10_040_06", "cl10_040_07", "cl10_040_08",
+    "cl10_040_09", "cl10_040_10", "cl10_060_01", "cl10_060_02", "cl10_060_03",
+    "cl10_060_04", "cl10_060_05", "cl10_060_06", "cl10_060_07", "cl10_060_08",
+    "cl10_060_09", "cl10_060_10", "cl10_080_01", "cl10_080_02", "cl10_080_03",
+    "cl10_080_04", "cl10_080_05", "cl10_080_06", "cl10_080_07", "cl10_080_08",
+    "cl10_080_09", "cl10_080_10", "cl10_100_01", "cl10_100_02", "cl10_100_03",
+    "cl10_100_04", "cl10_100_05", "cl10_100_06", "cl10_100_07", "cl10_100_08",
+    "cl10_100_09", "cl10_100_10")
+
+
+def __divide_based_on_size(instances: Iterable[str],
+                           divisions: int) -> list[list[str]]:
+    """
+    Divide the instances based on their size.
+
+    :param instances: the instances
+    :param divisions: the number of divisions
+    :return: the divided instances
+    """
+    insts: list[str] = list(instances)
+    if len(insts) <= 0:
+        return [insts]
+
+    try:
+        loaded: list[Instance] = [Instance.from_resource(n) for n in insts]
+    except (OSError, ValueError):
+        return [insts]
+
+    # get the quantiles = group divisions
+    qants: list[float | int] = quantiles((
+        inst.n_items for inst in loaded), n=divisions, method="inclusive")
+
+    # now put the instances into the groups
+    groups: list[list[str]] = []
+    for inst in loaded:
+        inst_size = inst.n_items
+        idx: int = 0
+        for q in qants:
+            if q > inst_size:
+                break
+            idx += 1
+        while idx >= len(groups):
+            groups.append([])
+        groups[idx].append(str(inst))
+
+    # remove useless groups
+    for idx in range(len(groups) - 1, -1, -1):
+        if len(groups[idx]) <= 0:
+            del groups[idx]
+    return groups
+
+
+def _make_instance_groups(instances: Iterable[str]) \
+        -> tuple[tuple[str, str | None, tuple[str, ...]], ...]:
+    """
+    Make the standard instance groups from an instance name list.
+
+    :return: the instance groups
+    """
+    groups: list[tuple[str, str | None, tuple[str, ...]]] = []
+
+    a_divided: list[list[str]] = __divide_based_on_size(sorted(
+        b for b in instances if b.startswith("a")
+        and (len(b) == 3) and b[-1].isdigit()
+        and b[-2].isdigit()), 3)
+    if len(a_divided) > 0:
+        subnames: list[str | None] = [None] if (len(a_divided) <= 1) else (
+            ["small", "large"] if (len(a_divided) <= 2) else
+            ["small", "med", "large"])
+        for a_idx, a_group in enumerate(a_divided):
+            v: tuple[str, str | None, tuple[str, ...]] = (
+                "a", subnames[a_idx], tuple(a_group))
+            if len(v[2]) > 0:
+                groups.append(v)
+
+    v = ("beng", "1-8", tuple(sorted(
+        b for b in instances if b.startswith("beng")
+        and (int(b[-2:]) < 9))))
+    if len(v[2]) > 0:
+        groups.append(v)
+
+    v = ("beng", "9-10", tuple(sorted(
+        b for b in instances if b.startswith("beng")
+        and (int(b[-2:]) >= 9))))
+    if len(v[2]) > 0:
+        groups.append(v)
+
+    for i in range(1, 11):
+        name: str = f"class {i}"
+        preprefix: str = f"cl0{i}" if i < 10 else f"cl{i}"
+        for n in (20, 40, 60, 80, 100):
+            prefix: str = f"{preprefix}_0{n}_" \
+                if n < 100 else f"{preprefix}_{n}_"
+            v = (name, str(n), tuple(sorted(
+                b for b in _INSTANCES if b.startswith(prefix))))
+            if len(v[2]) > 0:
+                groups.append(v)
+
+    v = ("asqas", None, tuple(sorted(
+        b for b in _INSTANCES if b.startswith("asqas"))))
+    if len(v[2]) > 0:
+        groups.append(v)
+
+    all_set: set[str] = set()
+    for g in groups:
+        all_set.update(g[2])
+    inst_set: set[str] = set(instances)
+    if all_set != inst_set:
+        raise ValueError(f"group instances is {all_set!r} but "
+                         f"instance set is {inst_set!r}!")
+    return tuple(groups)
+
+
+def __cutsq(matrix: np.ndarray) -> list[int]:
+    """
+    Cut all items into squares via the CUTSQ procedure.
+
+    :param matrix: the item matrix
+    :return: the list of squares
+
+    >>> __cutsq(np.array([[14, 12, 1]], int))
+    [12, 2, 2, 2, 2, 2, 2]
+
+    >>> __cutsq(np.array([[14, 12, 2]], int))
+    [12, 12, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
+    """
+    # create list of items in horizontal orientation
+    j_sq: Final[list[int]] = []  # the list of squares (width = height)
+    s: Final[list[int]] = []  # a temporary list
+    for row in matrix:
+        w: int = int(row[0])
+        h: int = int(row[1])
+        if h > w:
+            w, h = h, w
+        while h > 1:
+            k: int = w // h
+            for _ in range(k):
+                s.append(h)
+            w, h = h, w - (k * h)
+        times: int = int(row[2])
+        j_sq.extend(s * times if times > 1 else s)
+        s.clear()
+
+    j_sq.sort(reverse=True)  # sort the squares in decreasing size
+    return j_sq
+
+
+def __lb_q(bin_width: int, bin_height: int, q: int, j_js: list[int]) -> int:
+    """
+    Compute the lower bound for a given q.
+
+    :param bin_width: the bin width
+    :param bin_height: the bin height
+    :param q: the parameter q
+    :param j_js: the sorted square list
+    :return: the lower bound
+
+    >>> jj = [18, 18, 12, 12, 11, 11, 11, 11, 11, 7, 7, 7, 7, 7, 7]
+    >>> len(jj)
+    15
+    >>> __lb_q(23, 20, 6, jj)
+    6
+    """
+    m: Final[int] = len(j_js)
+    half_width: Final[float] = bin_width / 2
+    half_height: Final[float] = bin_height / 2
+    width_m_q: Final[int] = bin_width - q
+
+    # First we compute sets S1 to S4.
+    s1: list[int] = []  # S1 from Equation 2
+    s2: list[int] = []  # S2 from Equation 3
+    s3: list[int] = []  # S2 from Equation 4
+    s4: list[int] = []  # S2 from Equation 5
+
+    for i in range(m):
+        l_i: int = j_js[i]
+        if l_i > width_m_q:
+            s1.append(i)  # Equation 2
+        elif l_i > half_width:
+            s2.append(i)  # Equation 3
+        elif l_i > half_height:
+            s3.append(i)  # Equation 4
+        elif l_i >= q:
+            s4.append(i)  # Equation 5
+        else:
+            break
+
+    # compute set S23 as in Theorem 3 under Equation 7
+    height_m_q: Final[int] = bin_height - q
+    s23: Final[list[int]] = [j for j in (s2 + s3) if j_js[j] > height_m_q]
+
+    # Now we sort S2 by non-increasing value of residual space.
+    s2.reverse()  # = .sort(key=lambda i: bin_width - j_js[i], reverse=True)
+
+    # Now we compute S3 - ^S3^
+    s3_minus_s3d: list[int] = s3.copy()
+    for i in s2:
+        residual: int = bin_width - j_js[i]
+        not_found: bool = True
+        for j, idx in enumerate(s3_minus_s3d):
+            needs: int = j_js[idx]
+            if needs <= residual:
+                del s3_minus_s3d[j]
+                not_found = False
+                break
+        if not_found:
+            break
+
+    sum_s3_l: int = sum(j_js[i] for i in s3_minus_s3d)
+    b1 = sum_s3_l // bin_width
+    if (b1 * bin_width) < sum_s3_l:
+        b1 = b1 + 1
+
+    len_s3: int = len(s3_minus_s3d)
+    div: int = bin_width // ((bin_height // 2) + 1)
+    b2 = len_s3 // div
+    if (b2 * div) < len_s3:
+        b2 = b2 + 1
+
+    l_tilde: Final[int] = len(s2) + max(b1, b2)  # Equation 6.
+    bound: int = len(s1) + l_tilde
+
+    # Now compute the final bound based on Theorem 3 / Equation 7.
+    bin_size: Final[int] = bin_width * bin_height
+    denom: int = sum(j_js[i] ** 2 for i in (s2 + s3 + s4)) \
+        - ((bin_size * l_tilde) - sum(j_js[i] * (
+            bin_height - j_js[i]) for i in s23))
+    if denom > 0:
+        b = denom // bin_size
+        if (b * bin_size) < denom:
+            b = b + 1
+        bound = bound + b
+
+    return bound
+
+
+def _lower_bound_damv(bin_width: int, bin_height: int,
+                      matrix: np.ndarray) -> int:
+    """
+    Compute the lower bound as defined by Dell'Amico et al.
+
+    :param bin_width: the bin width
+    :param bin_height: the bin height
+    :param matrix: the item matrix
+    :return: the lower bound
+
+    >>> mat = np.array([[10, 5, 1], [3, 3, 1], [3, 3, 1]])
+    >>> _lower_bound_damv(23, 20, mat)
+    1
+
+    >>> mat = np.array([[20, 5, 3], [13, 23, 1], [13, 9, 3]])
+    >>> _lower_bound_damv(23, 20, mat)
+    3
+    """
+    # ensure horizontal orientation (width >= height)
+    if bin_height > bin_width:
+        bin_width, bin_height = bin_height, bin_width
+    j_sq: Final[list[int]] = __cutsq(matrix)
+    res: Final[int] = max(__lb_q(bin_width, bin_height, q, j_sq)
+                          for q in range((bin_height // 2) + 1))
+    return max(1, res)
+
+
+
+[docs] +class Instance(Component, np.ndarray): + """ + An instance of the 2D Bin Packing Problem. + + Each row of the matrix contains three values: 1. the item's width, 2. the + item's height, 3. how often the item occurs. + """ + + #: the name of the instance + name: str + #: the total number of items (including repetitions) + n_items: int + #: the number of different items + n_different_items: int + #: the total area occupied by all items + total_item_area: int + #: the bin width + bin_width: int + #: the bin height + bin_height: int + #: the minimum number of bins that this instance requires + lower_bound_bins: int + + def __new__(cls, name: str, + bin_width: int, bin_height: int, + matrix: np.ndarray | list[list[int]]) -> "Instance": + """ + Create an instance of the 2D bin packing problem. + + :param cls: the class + :param name: the name of the instance + :param bin_width: the bin width + :param bin_height: the bin height + :param matrix: the matrix with the data (will be copied); rows have + the form `(width, height, repetitions)` + """ + use_name: Final[str] = sanitize_name(name) + if name != use_name: + raise ValueError(f"Name {name!r} is not a valid name.") + + check_int_range(bin_width, "bin_width", 1, 1_000_000_000_000) + check_int_range(bin_height, "bin_height", 1, 1_000_000_000_000) + max_dim: Final[int] = max(bin_width, bin_height) + min_dim: Final[int] = min(bin_width, bin_height) + + n_different_items: Final[int] = check_int_range( + len(matrix), "n_different_items", 1, 100_000_000) + use_shape: Final[tuple[int, int]] = (n_different_items, 3) + + if isinstance(matrix, np.ndarray): + if not npu.is_np_int(matrix.dtype): + raise ValueError( + "Matrix must have an integer type, but is of type " + f"{str(matrix.dtype)!r} in instance {name!r}.") + if matrix.shape != use_shape: + raise ValueError( + f"Invalid shape {str(matrix.shape)!r} of matrix: " + "must have three columns and two dimensions, must be " + f"equal to {use_shape} in instance {name!r}.") + elif not isinstance(matrix, list): + raise type_error(matrix, "matrix", np.ndarray) + + n_items: int = 0 + max_size: int = -1 + item_area: int = 0 + for i in range(n_different_items): + row = matrix[i] + if not isinstance(row, list | np.ndarray): + raise type_error( + row, f"{row} at index {i} in {use_name!r}", + (list, np.ndarray)) + if len(row) != 3: + raise ValueError( + f"invalid row {row} at index {i} in {use_name!r}.") + width, height, repetitions = row + width = check_int_range(int(width), "width", 1, max_dim) + height = check_int_range(int(height), "height", 1, max_dim) + repetitions = check_int_range(int(repetitions), "repetitions", + 1, 100_000_000) + item_area += (width * height * repetitions) + max_size = max(max_size, width, height) + if (width > min_dim) and (height > min_dim): + raise ValueError( + f"object with width={width} and height={height} does " + f"not fit into bin with width={width} and " + f"height={height}.") + n_items += repetitions + + obj: Final[Instance] = super().__new__( + cls, use_shape, int_range_to_dtype( + min_value=0, max_value=max( + max_dim + max_size + 1, n_items + 1), force_signed=True)) + for i in range(n_different_items): + obj[i, :] = matrix[i] + + #: the name of the instance + obj.name = use_name + #: the number of different items + obj.n_different_items = n_different_items + #: the total number of items, i.e., the number of different items + #: multiplied with their repetition counts + obj.n_items = check_int_range( + n_items, "n_items", n_different_items, 1_000_000_000_000) + #: the height of the bins + obj.bin_height = bin_height + #: the width of the bins + obj.bin_width = bin_width + #: the total area occupied by all items + obj.total_item_area = item_area + +# We need at least as many bins such that their area is big enough +# for the total area of the items. + bin_area: int = bin_height * bin_width + lower_bound_geo: int = item_area // bin_area + if (lower_bound_geo * bin_area) < item_area: + lower_bound_geo += 1 + lower_bound_geo = check_int_range( + lower_bound_geo, "lower_bound_bins_geometric", + 1, 1_000_000_000_000) + +# We now compute the lower bound by Dell'Amico et al. + lower_bound_damv = check_int_range(_lower_bound_damv( + bin_width, bin_height, obj), "lower_bound_bins_damv", + 1, 1_000_000_000_000) + +# The overall computed lower bound is the maximum of the geometric and the +# Dell'Amico lower bound. + obj.lower_bound_bins = max(lower_bound_damv, lower_bound_geo) + return obj + + def __str__(self): + """ + Get the name of this instance. + + :return: the name of this instance + """ + return self.name + +
+[docs] + def get_standard_item_sequence(self) -> list[int]: + """ + Get the standardized item sequence. + + :return: the standardized item sequence + + >>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]]) + >>> ins.get_standard_item_sequence() + [1, 2, 3] + >>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 2]]) + >>> ins.get_standard_item_sequence() + [1, 2, 3, 3] + >>> ins = Instance("a", 100, 50, [[10, 5, 2], [3, 3, 3], [5, 5, 4]]) + >>> ins.get_standard_item_sequence() + [1, 1, 2, 2, 2, 3, 3, 3, 3] + """ + base_string: Final[list[int]] = [] + for i in range(1, self.n_different_items + 1): + for _ in range(self[i - 1, IDX_REPETITION]): + base_string.append(int(i)) + return base_string
+ + +
+[docs] + @staticmethod + def from_2dpacklib(file: str) -> "Instance": + """ + Load a problem instance from the 2dpacklib format. + + :param file: the file path + :return: the instance + """ + path: Final[Path] = file_path(file) + name: str = basename(path).lower() + if name.endswith(".ins2d"): + name = sanitize_name(name[:-6]) + + lines: Final[list[str]] = str.splitlines(path.read_all_str()) + n_different_items: Final[int] = check_to_int_range( + lines[0], "n_different_items", 1, 100_000_000) + + wh: Final[str] = lines[1] + spaceidx: Final[int] = wh.index(" ") + if spaceidx <= 0: + raise ValueError("Did not find space in second line.") + bin_width: Final[int] = check_to_int_range( + wh[:spaceidx], "bin_width", 1, 1_000_000_000_000) + bin_height: Final[int] = check_to_int_range( + wh[spaceidx + 1:], "bin_height", 1, 1_000_000_000_000) + del lines[0:2] + + max_dim: Final[int] = max(bin_width, bin_height) + data: Final[list[list[int]]] = [] + old_item_id: int = 0 + for line in lines: + text: list[str] = line.split(" ") + itemid: int = check_to_int_range( + text[0], "item-id", 1, n_different_items) + if itemid != (old_item_id + 1): + raise ValueError( + f"non-sequential item id {itemid} after {old_item_id}!") + old_item_id = itemid + width: int = check_to_int_range(text[1], "width", 1, max_dim) + height: int = check_to_int_range(text[2], "height", 1, max_dim) + count: int = 1 if len(text) <= 3 else \ + check_to_int_range(text[3], "count", 1, 1_000_000) + data.append([width, height, count]) + data.sort() + return Instance(name, bin_width, bin_height, data)
+ + +
+[docs] + def to_compact_str(self) -> str: + """ + Convert the instance to a compact string. + + The format of the string is a single line of semi-colon separated + values. The values are: `name;n_items;bin_width;bin_height`, + followed by the sequence of items, each in the form of + `;width,heigh[,times]`, where `times` is optional and only added + if the item occurs more than once. + + :return: a single line string with all the instance data + + >>> ins = Instance("x", 500, 50, [[3, 5, 1], [2, 5, 2]]) + >>> ins.to_compact_str() + 'x;2;500;50;3,5;2,5,2' + >>> ins.n_different_items + 2 + """ + lst: Final[list[str]] = [self.name, str(self.n_different_items), + str(self.bin_width), str(self.bin_height)] + for i in range(self.n_different_items): + width: int = self[i, IDX_WIDTH] + height: int = self[i, IDX_HEIGHT] + repetitions: int = self[i, IDX_REPETITION] + lst.append( + f"{width}{INTERNAL_SEP}{height}" if repetitions == 1 else + f"{width}{INTERNAL_SEP}{height}{INTERNAL_SEP}{repetitions}") + return CSV_SEPARATOR.join(lst)
+ + +
+[docs] + @staticmethod + def from_compact_str(data: str) -> "Instance": + """ + Transform a compact string back to an instance. + + :param data: the string data + :return: the instance + + >>> ins = Instance("x", 500, 50, [[3, 5, 1], [2, 5, 2]]) + >>> Instance.from_compact_str(ins.to_compact_str()).to_compact_str() + 'x;2;500;50;3,5;2,5,2' + """ + if not isinstance(data, str): + raise type_error(data, "data", str) + text: Final[list[str]] = data.split(CSV_SEPARATOR) + name: Final[str] = text[0] + n_different_items: Final[int] = check_to_int_range( + text[1], "n_different_items", 1, 100_000_000) + bin_width: Final[int] = check_to_int_range( + text[2], "bin_width", 1, 1_000_000_000_000) + bin_height: Final[int] = check_to_int_range( + text[3], "bin_height", 1, 1_000_000_000_000) + max_dim: Final[int] = max(bin_width, bin_height) + items: list[list[int]] = [] + for i in range(4, n_different_items + 4): + s: list[str] = text[i].split(INTERNAL_SEP) + row: list[int] = [ + check_to_int_range(s[IDX_WIDTH], "width", 1, max_dim), + check_to_int_range(s[IDX_HEIGHT], "height", 1, max_dim), + 1 if len(s) <= IDX_REPETITION else + check_to_int_range( + s[IDX_REPETITION], "times", 1, 100_000_000)] + items.append(row) + return Instance(name, bin_width, bin_height, items)
+ + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log the parameters describing this bin packing instance to the logger. + + :param logger: the logger for the parameters + + >>> from moptipy.utils.logger import InMemoryLogger + >>> with InMemoryLogger() as l: + ... with l.key_values("I") as kv: + ... Instance.from_resource("beng05").log_parameters_to(kv) + ... print(repr('@'.join(l.get_log()))) + 'BEGIN_I@name: beng05@class: moptipyapps.binpacking2d\ +.instance.Instance@nItems: 100@nDifferentItems: 100@binWidth: 25\ +@binHeight: 10@dtype: b@END_I' + """ + super().log_parameters_to(logger) + logger.key_value(N_ITEMS, self.n_items) + logger.key_value(N_DIFFERENT_ITEMS, self.n_different_items) + logger.key_value(BIN_WIDTH, self.bin_width) + logger.key_value(BIN_HEIGHT, self.bin_height) + logger.key_value(npu.KEY_NUMPY_TYPE, self.dtype.char)
+ + +
+[docs] + @staticmethod + def list_resources() -> tuple[str, ...]: + """ + Get a tuple of all the instances available as resource. + + :return: the tuple with the instance names + + >>> len(list(Instance.list_resources())) + 557 + """ + return _INSTANCES
+ + +
+[docs] + @staticmethod + def list_resources_groups() -> tuple[tuple[ + str, str | None, tuple[str, ...]], ...]: + """ + List the instance groups in the resources. + + One problem of the benchmark set for 2-dimensional bin packing is that + it has many instances: + + >>> len(Instance.list_resources()) + 557 + + With this function, we can group several of these instances together + in a way that is compliant with literature in order to then compute + statistics over these groups. Presenting data gathered over... + + >>> len(Instance.list_resources_groups()) + 56 + + ...groups is much easier than dealing with over 500 instances. + + :return: the instance groups, in a two level hierarchy. The result is + a sequence of tuples. Each tuple has the top-level group name and, + optionally, a second-level group name (or `None` if no second + level group exists). The third tuple element is a sequence of + instance names. + + >>> [(v[0], v[1], len(v[2])) for v in + ... Instance.list_resources_groups()] + [('a', 'small', 14), ('a', 'med', 14), ('a', 'large', 15), \ +('beng', '1-8', 8), ('beng', '9-10', 2), ('class 1', '20', 10), \ +('class 1', '40', 10), ('class 1', '60', 10), ('class 1', '80', 10), \ +('class 1', '100', 10), ('class 2', '20', 10), ('class 2', '40', 10), \ +('class 2', '60', 10), ('class 2', '80', 10), ('class 2', '100', 10), \ +('class 3', '20', 10), ('class 3', '40', 10), ('class 3', '60', 10), \ +('class 3', '80', 10), ('class 3', '100', 10), ('class 4', '20', 10), \ +('class 4', '40', 10), ('class 4', '60', 10), ('class 4', '80', 10), \ +('class 4', '100', 10), ('class 5', '20', 10), ('class 5', '40', 10), \ +('class 5', '60', 10), ('class 5', '80', 10), ('class 5', '100', 10), \ +('class 6', '20', 10), ('class 6', '40', 10), ('class 6', '60', 10), \ +('class 6', '80', 10), ('class 6', '100', 10), ('class 7', '20', 10), \ +('class 7', '40', 10), ('class 7', '60', 10), ('class 7', '80', 10), \ +('class 7', '100', 10), ('class 8', '20', 10), ('class 8', '40', 10), \ +('class 8', '60', 10), ('class 8', '80', 10), ('class 8', '100', 10), \ +('class 9', '20', 10), ('class 9', '40', 10), ('class 9', '60', 10), \ +('class 9', '80', 10), ('class 9', '100', 10), ('class 10', '20', 10), \ +('class 10', '40', 10), ('class 10', '60', 10), ('class 10', '80', 10), \ +('class 10', '100', 10), ('asqas', None, 4)] + """ + obj: object = Instance.list_resources_groups + attr: str = "__gs" + if not hasattr(obj, attr): + gs: tuple[tuple[str, str | None, tuple[str, ...]], ...] =\ + _make_instance_groups(Instance.list_resources()) + setattr(obj, attr, gs) + return gs + return cast(tuple[tuple[str, str | None, tuple[str, ...]], ...], + getattr(obj, attr))
+ + +
+[docs] + @staticmethod + def from_resource(name: str) -> "Instance": + """ + Load an instance from a resource. + + :param name: the name string + :return: the instance + + >>> Instance.from_resource("a01").to_compact_str() + 'a01;2;2750;1220;463,386,18;1680,420,6' + >>> Instance.from_resource("a07").to_compact_str() + 'a07;5;2750;1220;706,286,8;706,516,16;986,433,10;1120,486,\ +12;1120,986,12' + >>> Instance.from_resource("a08") is Instance.from_resource("a08") + True + >>> Instance.from_resource("a08") is Instance.from_resource("a09") + False + """ + if not isinstance(name, str): + raise type_error(name, "name", str) + container: Final = Instance.from_resource + inst_attr: Final[str] = f"__inst_{name}" + if hasattr(container, inst_attr): # instance loaded? + return cast(Instance, getattr(container, inst_attr)) + text_attr: Final[str] = f"__text_{INSTANCES_RESOURCE}" + text: list[str] + total_attr: Final[str] = "__total_insts" + if hasattr(container, text_attr): # ok, we got the text already + text = cast(list[str], getattr(container, text_attr)) + else: # the first time we load the text + with resources.files(__package__).joinpath( + INSTANCES_RESOURCE).open("r", encoding=UTF8) as stream: + text = [line for line in (line_raw.strip() for line_raw + in stream.readlines()) + if len(line) > 0] + setattr(container, text_attr, text) + setattr(container, total_attr, 0) # so far, no instance + + imax: int = len(text) + imin: int = 0 + while imin <= imax: # binary search for instance + imid: int = (imax + imin) // 2 + line: str = text[imid] + idx: int = line.index(CSV_SEPARATOR) + prefix: str = line[0:idx] + if prefix == name: + instance = Instance.from_compact_str(line) + setattr(container, inst_attr, instance) + got: int = getattr(container, total_attr) + got = got + 1 + if got >= len(_INSTANCES): # got all instances, can del text + delattr(container, total_attr) + delattr(container, text_attr) + return instance + if prefix < name: + imin = imid + 1 + else: + imax = imid - 1 + raise ValueError(f"instance {name!r} not found.")
+
+ +
diff --git a/_modules/moptipyapps/binpacking2d/instgen/.nojekyll b/_modules/moptipyapps/binpacking2d/instgen/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/moptipyapps/binpacking2d/instgen/errors.html b/_modules/moptipyapps/binpacking2d/instgen/errors.html new file mode 100644 index 00000000..8dcb1fb1 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/instgen/errors.html @@ -0,0 +1,261 @@ +moptipyapps.binpacking2d.instgen.errors — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.instgen.errors

+"""
+An objective function counting deviations from the instance definition.
+
+This objective function will be the smaller, the closer the structure
+of an instance is to the original instance.
+Due to our encoding, we create instances whose bin width, bin height,
+and the number of items is the same as in an existing instance. The
+lower bound for the required number of bins is also the same.
+
+This objective function here also incorporates some additional features, such
+as:
+
+1. Is the number of different items similar to those in the original instance?
+   In an existing instance, some items of same width and height could be
+   grouped together. We may have 10 items to pack, but only 3 different item
+   sizes exist. We here compare the number of different item sizes of a
+   generated instance with the number in the instance definition.
+2. In a given instance, we can observe the minimum and maximum width and
+   height of any item. If an item in the generated instance is smaller than
+   the minimum or larger than the maximum permitted value in one dimension,
+   this will increase the error count.
+3. Additionally, we want the actual minimum and maximum width and height of
+   any item in the generated instance is the same as in the original instance.
+4. Finally, we want that the total area covered by all items is the same in
+   the generated instance as in the original instance.
+
+All of these potential violations are counted and added up. Using this
+objective function should drive the search towards generating instances that
+are structurally similar to an existing instance, at least in some parameters.
+
+We could push this arbitrarily further, trying to emulate the exact
+distribution of the item sizes, etc. But this may just lead to the
+reproduction of the original instance by the search and not add anything
+interesting.
+
+>>> orig = Instance.from_resource("a04")
+>>> space = InstanceSpace(orig)
+>>> print(f"{space.inst_name!r} with {space.n_different_items}/"
+...       f"{space.n_items} items with area {space.total_item_area} "
+...       f"in {space.min_bins} bins of "
+...       f"size {space.bin_width}*{space.bin_height}.")
+'a04n' with 2/16 items with area 7305688 in 3 bins of size 2750*1220.
+
+>>> from moptipyapps.binpacking2d.instgen.inst_decoding import InstanceDecoder
+>>> decoder = InstanceDecoder(space)
+>>> import numpy as np
+>>> x = np.array([ 0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 15/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;15;2750;1220;1101,1098;2750,244;2750,98;1101,171;1649,171;2750,976;\
+441,122;1649,122;2750,10;2750,1,2;2750,3;1649,1098;2750,878;2750,58;660,122
+
+>>> errors = Errors(space)
+>>> errors.lower_bound()
+0.0
+>>> errors.upper_bound()
+1.0
+>>> errors.evaluate(y)
+0.3778740795710009
+
+>>> y[0] = orig
+>>> errors.evaluate(y)
+0.0
+"""
+from typing import Final
+
+from moptipy.api.objective import Objective
+from moptipy.utils.logger import KeyValueLogSection
+from pycommons.types import type_error
+
+from moptipyapps.binpacking2d.instance import (
+    IDX_HEIGHT,
+    IDX_REPETITION,
+    IDX_WIDTH,
+    Instance,
+)
+from moptipyapps.binpacking2d.instgen.instance_space import InstanceSpace
+
+
+
+[docs] +class Errors(Objective): + """Compute the deviation of an instance from the definition.""" + + def __init__(self, space: InstanceSpace) -> None: + """ + Initialize the errors objective function. + + :param instance: the instance to load the bounds from + """ + super().__init__() + if not isinstance(space, InstanceSpace): + raise type_error(space, "space", InstanceSpace) + #: the instance description + self.space: Final[InstanceSpace] = space + + # These errors are permitted. + max_errors: int = max( + space.n_different_items - 1, + space.n_items - space.n_different_items - 1) + + goal_min_width: Final[int] = space.item_width_min + goal_max_width: Final[int] = space.item_width_max + goal_min_height: Final[int] = space.item_height_min + goal_max_height: Final[int] = space.item_height_max + bin_width: Final[int] = space.bin_width + bin_height: Final[int] = space.bin_height + + n_items: Final[int] = space.n_items + max_errors += n_items * max( + goal_min_width - 1, bin_width - goal_max_width) + max_errors += n_items * max( + goal_min_height - 1, bin_height - goal_max_height) + max_errors += max(goal_min_width, bin_width - goal_min_width) + max_errors += max(goal_max_width, bin_width - goal_max_width) + max_errors += max(goal_min_height, bin_height - goal_min_height) + max_errors += max(goal_max_height, bin_height - goal_max_height) + + alt_area: Final[int] = (space.min_bins * bin_width * bin_height + - space.total_item_area) + if alt_area < 0: + raise ValueError("Invalid item area in space?") + max_errors += max(space.total_item_area, alt_area) + + #: the maximum number of errors + self.__max_errors: Final[int] = max_errors + +
+[docs] + def evaluate(self, x: list[Instance] | Instance) -> float: + """ + Compute the deviations from the instance definition. + + :param x: the instance + :return: the number of deviations divided by the maximum of + the deviations + """ + errors: int = 0 + inst: Final[Instance] = x[0] if isinstance(x, list) else x + space: Final[InstanceSpace] = self.space + + # Some errors are not permitted. + errors += abs(inst.bin_width - space.bin_width) # should be 0! + errors += abs(inst.bin_height - space.bin_height) # should be 0! + errors += abs(inst.n_items - space.n_items) # should be 0! + if errors > 0: + raise ValueError( + f"Instance {inst} is inconsistent with space {space}.") + + # These errors are permitted. + n_different: Final[int] = inst.n_different_items + errors += abs(n_different - space.n_different_items) # > 0 + goal_min_width: Final[int] = space.item_width_min + goal_max_width: Final[int] = space.item_width_max + goal_min_height: Final[int] = space.item_height_min + goal_max_height: Final[int] = space.item_height_max + + actual_min_width: int = space.bin_width + actual_max_width: int = 0 + actual_min_height: int = space.bin_height + actual_max_height: int = 0 + total_area: int = 0 + + for row in range(n_different): + width: int = int(inst[row, IDX_WIDTH]) + height: int = int(inst[row, IDX_HEIGHT]) + actual_min_width = min(actual_min_width, width) + actual_max_width = max(actual_max_width, width) + actual_min_height = min(actual_min_height, height) + actual_max_height = max(actual_max_height, height) + + n: int = int(inst[row, IDX_REPETITION]) + total_area += n * width * height + if width < goal_min_width: + errors += n * (goal_min_width - width) + elif width > goal_max_width: + errors += n * (width - goal_max_width) + if height < goal_min_height: + errors += n * (goal_min_height - height) + elif height > goal_max_height: + errors += n * (height - goal_max_height) + + errors += abs(actual_min_width - goal_min_width) + errors += abs(actual_max_width - goal_max_width) + errors += abs(actual_min_height - goal_min_height) + errors += abs(actual_max_height - goal_max_height) + + if total_area != inst.total_item_area: + raise ValueError("Invalid total area?") + errors += abs(total_area - space.total_item_area) + + return max(0.0, min(1.0, errors / self.__max_errors))
+ + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log the parameters of this instance. + + :param logger: the logger + """ + super().log_parameters_to(logger) + logger.key_value("maxErrors", self.__max_errors)
+ + +
+[docs] + def lower_bound(self) -> float: + """ + Get the lower bound of the instance template deviations. + + :returs 0.0: always + """ + return 0.0
+ + +
+[docs] + def is_always_integer(self) -> bool: + """ + Return `True` because there are only integer errors. + + :retval False: always + """ + return False
+ + +
+[docs] + def upper_bound(self) -> float: + """ + Get the upper bound of the number of deviations. + + :returs 1.0: always + """ + return 1.0
+ + + def __str__(self) -> str: + """ + Get the name of the errors objective function. + + :return: `errors` + :retval "errors": always + """ + return "errors"
+ +
diff --git a/_modules/moptipyapps/binpacking2d/instgen/errors_and_hardness.html b/_modules/moptipyapps/binpacking2d/instgen/errors_and_hardness.html new file mode 100644 index 00000000..85b34843 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/instgen/errors_and_hardness.html @@ -0,0 +1,156 @@ +moptipyapps.binpacking2d.instgen.errors_and_hardness — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.instgen.errors_and_hardness

+"""
+A combination of the errors and the hardness objective.
+
+>>> orig = Instance.from_resource("a04")
+>>> space = InstanceSpace(orig)
+>>> print(f"{space.inst_name!r} with {space.n_different_items}/"
+...       f"{space.n_items} items with area {space.total_item_area} "
+...       f"in {space.min_bins} bins of "
+...       f"size {space.bin_width}*{space.bin_height}.")
+'a04n' with 2/16 items with area 7305688 in 3 bins of size 2750*1220.
+
+>>> from moptipyapps.binpacking2d.instgen.inst_decoding import InstanceDecoder
+>>> decoder = InstanceDecoder(space)
+>>> import numpy as np
+>>> x = np.array([ 0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 15/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;15;2750;1220;1101,1098;2750,244;2750,98;1101,171;1649,171;2750,976;\
+441,122;1649,122;2750,10;2750,1,2;2750,3;1649,1098;2750,878;2750,58;660,122
+
+>>> obj = ErrorsAndHardness(space, max_fes=100)
+>>> obj.lower_bound()
+0.0
+>>> obj.upper_bound()
+1.0
+>>> obj.evaluate(y)
+0.8776461858988774
+
+>>> obj.evaluate(orig)
+0.9866528870384179
+"""
+from typing import Callable, Final, Iterable
+
+from moptipy.api.execution import Execution
+from moptipy.api.objective import Objective
+from moptipy.utils.logger import KeyValueLogSection
+
+from moptipyapps.binpacking2d.instance import (
+    Instance,
+)
+from moptipyapps.binpacking2d.instgen.errors import Errors
+from moptipyapps.binpacking2d.instgen.hardness import (
+    DEFAULT_EXECUTORS,
+    Hardness,
+)
+from moptipyapps.binpacking2d.instgen.instance_space import InstanceSpace
+
+
+
+[docs] +class ErrorsAndHardness(Objective): + """Compute the errors and hardness.""" + + def __init__(self, space: InstanceSpace, + max_fes: int = 1_000_000, n_runs: int = 3, + executors: Iterable[ + Callable[[Instance], tuple[ + Execution, Objective]]] = DEFAULT_EXECUTORS) -> None: + """ + Initialize the errors objective function. + + :param space: the instance space + :param max_fes: the maximum FEs + :param n_runs: the maximum runs + :param executors: the functions creating the executions + """ + super().__init__() + #: the errors objective + self.errors: Final[Errors] = Errors(space) + #: the hardness objective function + self.hardness: Final[Hardness] = Hardness( + max_fes, n_runs, executors) + +
+[docs] + def evaluate(self, x: list[Instance] | Instance) -> float: + """ + Compute the combination of hardness and errors. + + :param x: the instance + :return: the hardness and errors + """ + return max(0.0, min(1.0, ((self.hardness.evaluate( + x) * 1000.0) + self.errors.evaluate(x)) / 1001.0))
+ + +
+[docs] + def lower_bound(self) -> float: + """ + Get the lower bound of the instance error and hardness. + + :returns 0.0: always + """ + return 0.0
+ + +
+[docs] + def upper_bound(self) -> float: + """ + Get the upper bound of the instance error and hardness. + + :returns 1.0: always + """ + return 1.0
+ + +
+[docs] + def is_always_integer(self) -> bool: + """ + Return `False` because the hardness function returns `float`. + + :retval False: always + """ + return False
+ + + def __str__(self) -> str: + """ + Get the name of the errors objective function. + + :return: `errorsAndHardness` + :retval "errorsAndHardness": always + """ + return "errorsAndHardness" + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log the parameters of this instance. + + :param logger: the logger + """ + super().log_parameters_to(logger) + with logger.scope("err") as err: + self.errors.log_parameters_to(err) + with logger.scope("hard") as hard: + self.hardness.log_parameters_to(hard)
+
+ +
diff --git a/_modules/moptipyapps/binpacking2d/instgen/experiment.html b/_modules/moptipyapps/binpacking2d/instgen/experiment.html new file mode 100644 index 00000000..4612a120 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/instgen/experiment.html @@ -0,0 +1,101 @@ +moptipyapps.binpacking2d.instgen.experiment — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.instgen.experiment

+"""An example experiment for generating bin packing instances."""
+
+import argparse
+from typing import Callable, Final, cast
+
+from moptipy.algorithms.so.vector.cmaes_lib import BiPopCMAES
+from moptipy.api.execution import Execution
+from moptipy.api.experiment import run_experiment
+from pycommons.io.path import Path
+
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.instgen.errors_and_hardness import (
+    ErrorsAndHardness,
+)
+from moptipyapps.binpacking2d.instgen.problem import Problem
+from moptipyapps.shared import moptipyapps_argparser
+
+#: the maximum number of FEs
+MAX_FES: Final[int] = 10_000
+
+#: the maximum number of FEs in for the algorithm runs inside the objective
+INNER_MAX_FES: Final[int] = 100_000
+
+#: the numbe rof runs of the algorithms inside the objective
+INNER_RUNS: Final[int] = 3
+
+
+
+[docs] +def cmaes(problem: Problem) -> Execution: + """ + Create the basic BiPop-CMA-ES setup. + + :param problem: the problem to solve + :return: the execution + """ + return (Execution() + .set_algorithm(BiPopCMAES(problem.search_space)) + .set_search_space(problem.search_space) + .set_solution_space(problem.solution_space) + .set_encoding(problem.encoding) + .set_objective(ErrorsAndHardness( + problem.solution_space, INNER_MAX_FES, INNER_RUNS)) + .set_max_fes(MAX_FES) + .set_log_improvements(True))
+ + + +#: the instances to use as blueprints +__USE_INSTANCES: Final[tuple[str, ...]] = ( + "beng01", "beng02", "beng03", "beng04", "beng05", "beng06", "beng07", + "beng08", "beng09", "beng10", "cl01_020_01", "cl01_040_01", "cl01_060_01", + "cl01_080_01", "cl01_100_01", "cl02_020_01", "cl02_040_01", "cl02_060_01", + "cl02_080_01", "cl02_100_01", "cl03_020_01", "cl03_040_01", "cl03_060_01", + "cl03_080_01", "cl03_100_01", "cl04_020_01", "cl04_040_01", "cl04_060_01", + "cl04_080_01", "cl04_100_01", "cl05_020_01", "cl05_040_01", "cl05_060_01", + "cl05_080_01", "cl05_100_01", "cl06_020_01", "cl06_040_01", "cl06_060_01", + "cl06_080_01", "cl06_100_01", "cl07_020_01", "cl07_040_01", "cl07_060_01", + "cl07_080_01", "cl07_100_01", "cl08_020_01", "cl08_040_01", "cl08_060_01", + "cl08_080_01", "cl08_100_01", "cl09_020_01", "cl09_040_01", "cl09_060_01", + "cl09_080_01", "cl09_100_01", "cl10_020_01", "cl10_040_01", "cl10_060_01", + "cl10_080_01", "cl10_100_01", "cl10_100_10") + + +
+[docs] +def run(base_dir: str) -> None: + """ + Run the experiment. + + :param base_dir: the base directory + """ + use_dir: Final[Path] = Path(base_dir) + use_dir.ensure_dir_exists() + + inst_creators: list[Callable[[], Instance]] = [cast( + Callable[[], Instance], lambda __s=_s, __t=_t: Problem(__s, __t)) + for _s in __USE_INSTANCES for _t in (0.25, 0.125)] + + run_experiment( + base_dir=use_dir, + instances=inst_creators, + setups=[cmaes], + n_runs=[1, 2, 3, 5, 7, 11, 13, 17, 20, 23, 29, 30, 31], + perform_warmup=False, + perform_pre_warmup=False)
+ + + +# Run the experiment from the command line +if __name__ == "__main__": + parser: Final[argparse.ArgumentParser] = moptipyapps_argparser( + __file__, "2D Bin Packing Instance Generator", + "Run the 2D Bin Packing Instance Generator experiment.") + parser.add_argument( + "dest", help="the directory to store the experimental results under", + type=Path, nargs="?", default="./results/") + args: Final[argparse.Namespace] = parser.parse_args() + run(args.dest) +
diff --git a/_modules/moptipyapps/binpacking2d/instgen/hardness.html b/_modules/moptipyapps/binpacking2d/instgen/hardness.html new file mode 100644 index 00000000..b2dfa174 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/instgen/hardness.html @@ -0,0 +1,315 @@ +moptipyapps.binpacking2d.instgen.hardness — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.instgen.hardness

+"""
+An objective function assessing the hardness of an instance.
+
+>>> from moptipyapps.binpacking2d.instgen.instance_space import InstanceSpace
+>>> orig = Instance.from_resource("a04")
+>>> space = InstanceSpace(orig)
+>>> print(f"{space.inst_name!r} with {space.n_different_items}/"
+...       f"{space.n_items} items with area {space.total_item_area} "
+...       f"in {space.min_bins} bins of "
+...       f"size {space.bin_width}*{space.bin_height}.")
+'a04n' with 2/16 items with area 7305688 in 3 bins of size 2750*1220.
+
+>>> from moptipyapps.binpacking2d.instgen.inst_decoding import InstanceDecoder
+>>> decoder = InstanceDecoder(space)
+>>> import numpy as np
+>>> x = np.array([ 0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 15/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;15;2750;1220;1101,1098;2750,244;2750,98;1101,171;1649,171;2750,976;\
+441,122;1649,122;2750,10;2750,1,2;2750,3;1649,1098;2750,878;2750,58;660,122
+
+>>> hardness = Hardness(max_fes=100)
+>>> hardness.lower_bound()
+0.0
+>>> hardness.upper_bound()
+1.0
+>>> hardness.evaluate(y)
+0.8781459580052053
+
+>>> y[0] = orig
+>>> hardness.evaluate(y)
+0.9876395399254564
+
+>>> z = Instance.from_compact_str(
+...     "cl04_020_01n;19;100;100;1,10;2,38;2,62;1,4,2;3,38;1,7;27,93;1,62;1,"
+...     "3;13,38;1,38;1,17;1,45;36,62;39,3;1,2;20,10;3,24;12,4")
+>>> hardness.evaluate(z)
+0.9995959203471327
+"""
+from math import isfinite
+from typing import Callable, Final, Iterable
+
+from moptipy.algorithms.random_sampling import RandomSampling
+from moptipy.algorithms.so.rls import RLS
+from moptipy.api.execution import Execution
+from moptipy.api.objective import Objective
+from moptipy.operators.signed_permutations.op0_shuffle_and_flip import (
+    Op0ShuffleAndFlip,
+)
+from moptipy.operators.signed_permutations.op1_swap_2_or_flip import (
+    Op1Swap2OrFlip,
+)
+from moptipy.spaces.signed_permutations import SignedPermutations
+from moptipy.utils.logger import KeyValueLogSection
+from moptipy.utils.nputils import rand_seeds_from_str
+from pycommons.types import check_int_range
+
+from moptipyapps.binpacking2d.encodings.ibl_encoding_1 import (
+    ImprovedBottomLeftEncoding1,
+)
+from moptipyapps.binpacking2d.instance import (
+    Instance,
+)
+from moptipyapps.binpacking2d.objectives.bin_count import BinCount
+from moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline import (
+    BinCountAndLastSkyline,
+)
+from moptipyapps.binpacking2d.packing_space import PackingSpace
+
+
+
+[docs] +def setup_rls_f7(instance: Instance) -> tuple[Execution, Objective]: + """ + Set up the randomized local search for an instance. + + :param instance: the instance + :return: the execution and upper bound of the objective + """ + search_space = SignedPermutations( + instance.get_standard_item_sequence()) # Create the search space. + solution_space = PackingSpace(instance) # Create the space of packings. + objective: Final[BinCountAndLastSkyline] = BinCountAndLastSkyline(instance) + + # Build a single execution of a single run of a single algorithm and + # return the upper bound of the objective value + return (Execution() + .set_search_space(search_space) + .set_solution_space(solution_space) + .set_encoding(ImprovedBottomLeftEncoding1(instance)) + .set_algorithm( # This is the algorithm: Randomized Local Search. + RLS(Op0ShuffleAndFlip(search_space), Op1Swap2OrFlip())) + .set_objective(objective), objective)
+ + + +
+[docs] +def setup_rls_f1(instance: Instance) -> tuple[Execution, Objective]: + """ + Set up the randomized local search for an instance. + + :param instance: the instance + :return: the execution + """ + search_space = SignedPermutations( + instance.get_standard_item_sequence()) # Create the search space. + solution_space = PackingSpace(instance) # Create the space of packings. + objective: Final[BinCount] = BinCount(instance) + + # Build a single execution of a single run of a single algorithm and + # return the lower bound of the objective value + return (Execution() + .set_search_space(search_space) + .set_solution_space(solution_space) + .set_encoding(ImprovedBottomLeftEncoding1(instance)) + .set_algorithm( # This is the algorithm: Randomized Local Search. + RLS(Op0ShuffleAndFlip(search_space), Op1Swap2OrFlip())) + .set_objective(objective), objective)
+ + + +
+[docs] +def setup_rs_f1(instance: Instance) -> tuple[Execution, Objective]: + """ + Set up the randomized sampling for an instance. + + :param instance: the instance + :return: the execution + """ + search_space = SignedPermutations( + instance.get_standard_item_sequence()) # Create the search space. + solution_space = PackingSpace(instance) # Create the space of packings. + objective: Final[BinCount] = BinCount(instance) + + # Build a single execution of a single run of a single algorithm and + # return the lower bound of the objective value + return (Execution() + .set_search_space(search_space) + .set_solution_space(solution_space) + .set_encoding(ImprovedBottomLeftEncoding1(instance)) + .set_algorithm( # This is the algorithm: Random Sampling + RandomSampling(Op0ShuffleAndFlip(search_space))) + .set_objective(objective), objective)
+ + + +#: the default executors +DEFAULT_EXECUTORS: Final[tuple[Callable[[Instance], tuple[ + Execution, Objective]], ...]] = ( + setup_rs_f1, setup_rls_f1, setup_rls_f7) + + +
+[docs] +class Hardness(Objective): + """Compute the hardness of an instance.""" + + def __init__( + self, max_fes: int = 1_000_000, n_runs: int = 3, + executors: Iterable[Callable[[Instance], tuple[ + Execution, Objective]]] = DEFAULT_EXECUTORS) -> None: + """ + Initialize the hardness objective function. + + :param max_fes: the maximum FEs + :param n_runs: the maximum runs + :param executors: the functions creating the executions + """ + super().__init__() + #: the maximum FEs per setup. + self.max_fes: Final[int] = check_int_range( + max_fes, "max_fes", 2, 1_000_000_000_000) + #: the maximum FEs per setup. + self.n_runs: Final[int] = check_int_range( + n_runs, "n_runs", 1, 1_000_000) + #: the executors + self.executors: Final[tuple[Callable[[Instance], tuple[ + Execution, Objective]], ...]] = tuple(executors) + + #: the last instance name + self.__last_inst: str | None = None + #: the last seeds name + self.__last_seeds: tuple[int, ...] | None = None + +
+[docs] + def evaluate(self, x: list[Instance] | Instance) -> float: + """ + Compute the hardness of an instance. + + :param x: the instance + :return: the hardness + """ + instance: Final[Instance] = x[0] if isinstance(x, list) else x + seeds: tuple[int, ...] + + name: str = instance.name + if (self.__last_seeds is None) or (self.__last_inst is None) or ( + self.__last_inst != name): + self.__last_seeds = seeds = tuple(rand_seeds_from_str( + f"seed for {instance.name}", self.n_runs)) + self.__last_inst = name + else: + seeds = self.__last_seeds + + max_fes: Final[int] = self.max_fes + runs: int = 0 + result: float = 0.0 + for executor in self.executors: + execs, f = executor(instance) + lb: int | float = f.lower_bound() + ub: int | float = f.upper_bound() + if not (isfinite(lb) and isfinite(ub) and (lb < ub)): + raise ValueError(f"Invalid lower and upper bound {lb}, {ub}.") + execs.set_max_fes(max_fes) + for seed in seeds: + execs.set_rand_seed(seed) + with execs.execute() as proc: + runs += 1 + quality: int | float = proc.get_best_f() + if not (isfinite(quality) and (lb <= quality <= ub)): + raise ValueError( + f"quality={quality} invalid, must be in " + f"[{lb}, {ub}] for objective {f}.") + quality = (ub - quality) / (ub - lb) + if not (0.0 <= quality <= 1.0): + raise ValueError( + f"invalid normalized quality {quality} " + f"for objective {f}.") + runtime: int | float = proc.get_last_improvement_fe() + if not (0 < runtime <= max_fes): + raise ValueError(f"invalid FEs {runtime}, must " + f"be in 1..{max_fes}.") + runtime = (max_fes - runtime) / (max_fes - 1) + if not (0.0 <= runtime <= 1.0): + raise ValueError( + f"invalid normalized runtime {runtime}.") + result += max(0.0, min(1.0, ( + (quality * 1000.0) + runtime) / 1001.0)) + return max(0.0, min(1.0, result / runs))
+ + +
+[docs] + def lower_bound(self) -> float: + """ + Get the lower bound of the instance hardness. + + :return: the lower bound for the instance hardness + :returns 0.0: always + """ + return 0.0
+ + +
+[docs] + def upper_bound(self) -> float: + """ + Get the upper bound of the instance hardness. + + :return: the upper bound for the instance hardness + :returns 1.0: always + """ + return 1.0
+ + +
+[docs] + def is_always_integer(self) -> bool: + """ + Return `False` because the hardness function returns `float`. + + :retval False: always + """ + return False
+ + + def __str__(self) -> str: + """ + Get the name of the hardness objective function. + + :return: `hardness` + :retval "hardness": always + """ + return "hardness" + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log the parameters of this instance. + + :param logger: the logger + """ + super().log_parameters_to(logger) + logger.key_value("nRuns", self.n_runs) + logger.key_value("maxFEs", self.max_fes) + logger.key_value("nExecutors", len(self.executors))
+
+ +
diff --git a/_modules/moptipyapps/binpacking2d/instgen/inst_decoding.html b/_modules/moptipyapps/binpacking2d/instgen/inst_decoding.html new file mode 100644 index 00000000..9125284c --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/instgen/inst_decoding.html @@ -0,0 +1,563 @@ +moptipyapps.binpacking2d.instgen.inst_decoding — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.instgen.inst_decoding

+"""
+A decoder for 2D BPP instances.
+
+The goal of developing this decoding procedure is that we need a deterministic
+mapping of some easy-to-process data structure to an instance of the
+two-dimensional bin packing problem. The instance produced by the mapping
+should use a pre-defined bin width, bin height, and number of items. It should
+also have a pre-defined lower bound for the number of bins required and it
+must be ensured that this lower bound can also be reached, i.e., that at least
+one solution exists that can indeed pack all items into this number of bins.
+
+As source data structure to be mapped, we choose the real vectors of a fixed
+length (discussed later on).
+
+The idea is that we take the bin width, bin height, and lower bound of the
+number of bins (let's call it `lb`) from a template instance. We also take
+the number items (let's call it `n`) from that instance.
+
+Now we begin with `lb` items, each of which exactly of the size and dimensions
+of a bin. At the end, we want to have `n` items. To get there, in each step of
+our decoding, we split one existing item into two items. This means that each
+step will create one additional item for the instance (while making one
+existing item smaller). This, in turn, means that we have to do `n - lb`
+decoding steps, as we start with `lb` items and, after `n - lb` steps, will
+have `lb + n - lb = n` items.
+
+So far so good.
+But how do we split?
+
+Each split that we want to perform be defined by four features:
+
+1. the index of the item that we are going to split,
+2. whether we split it horizontally or vertically,
+3. where we are going to split it,
+4. and how to continue if the proposed split is not possible, e.g., because
+   it would lead to a zero-width or zero-height item.
+
+Now we can encode this in two real numbers `selector` and `cutter` from the
+interval `[-1,1]`.
+
+First, we multiply the absolute value of the `selector` with the current
+number of items that we have. This is initially `2`, will then be `3` in the
+next iteration, then `4`, and so on.
+Converted to an int, the result of this multiplication gives us the index of
+the item to split.
+
+Then, if `cutter >= 0`, we will cut the item horizontally. Otherwise, i.e., if
+`cutter < 0`, we cut vertically.
+Where to cut is then decided by multiplying the absolute value of `cutter`
+with the length of the item in the selected cutting dimension.
+
+If that is not possible, we move to the next item and try again.
+If `selector < 0`, we move towards smaller indices and wrap after `0`.
+Otherwise, we move towards higher indices and wrap at the end of the item
+list.
+If we arrive back at the first object, this means that the split was not
+possible for any of the existing items.
+We now rotate the split by 90°, i.e., if we tried horizontal splits, we now
+try vertical ones and vice versa.
+
+It is easy to see that it must always be possible to split at least one item
+in at least one direction. Since we took the bin dimensions and numbers of
+items from an existing instance of the benchmark set, it must be possible to
+divide the bins into `n` items in one way or another. Therefore, the loop
+will eventually terminate and yield the right amount of items.
+
+This means that with `2 * (n - lb)` floating point numbers, we can describe an
+instance whose result is a perfect packing, without any wasted space.
+
+Now the benchmark instances are not just instances that can only be solved by
+perfect packings. Instead, they have some wasted space.
+
+We now want to add some wasted space to our instance. So far, our items occupy
+exactly `lb * bin_width * bin_height` space units. We can cut at most
+`bin_width * bin_height - 1` of these units without changing the required bins
+of the packing: We would end up with `(lb - 1) * bin_width * bin_height + 1`
+space units required by our items, which is `1` too large to fit into `lb - 1`
+bins.
+
+So we just do the same thing again:
+We again use two real numbers to describe each split.
+Like before, we loop through these numbers, pick the object to split, and
+compute where to split it. Just now we throw away the piece that was cut off.
+(Of course, we compute the split positions such that we never dip under the
+area requirement discussed above).
+
+This allows us to use additional real variables to define how the space should
+be reduced. `2 * (n - lb)` variables, we get instances requiring perfect
+packing. With every additional pair of variables, we cut some more space.
+If we would use `2 * (n - lb) + 10` variables, then we would try to select
+five items from which we can cut off a bit. This number of additional
+variables can be chosen by the user.
+
+Finally, we merge all items that have the same dimension into groups, as is
+the case in some of the original instances. We then shuffle these groups
+randomly, to give the instances a bit of a more unordered texture.
+The random shuffling is seeded with the binary representation of the input
+vectors.
+
+In the end, we have translated a real vector to a two-dimensional bin packing
+instance. Hurray.
+
+>>> space = InstanceSpace(Instance.from_resource("a04"))
+>>> print(f"{space.inst_name!r} with {space.n_different_items}/"
+...       f"{space.n_items} items with area {space.total_item_area} "
+...       f"in {space.min_bins} bins of "
+...       f"size {space.bin_width}*{space.bin_height}.")
+'a04n' with 2/16 items with area 7305688 in 3 bins of size 2750*1220.
+
+>>> decoder = InstanceDecoder(space)
+>>> import numpy as np
+>>> x = np.array([ 0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 15/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;15;2750;1220;1101,1098;2750,244;2750,98;1101,171;1649,171;2750,976;\
+441,122;1649,122;2750,10;2750,1,2;2750,3;1649,1098;2750,878;2750,58;660,122
+
+
+>>> x = np.array([ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 3/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;3;2750;1220;2750,1216,2;2750,1,13;2750,1215
+
+>>> from math import nextafter
+>>> a1 = nextafter(1.0, -10)
+>>> x = np.array([ a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 4/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;4;2750;1220;2750,1208;2750,1219;2750,1220;2750,1,13
+
+>>> from math import nextafter
+>>> a1 = nextafter(-1.0, 10)
+>>> x = np.array([ a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 5/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;5;2750;1220;2750,1220;2730,1220;2748,1220;1,1220,4;2,1220,9
+
+>>> from math import nextafter
+>>> a1 = nextafter(-1.0, 10)
+>>> x = np.array([ a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, 0.3, 0.7 ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 6/16 items with area 10064146 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;6;2750;1220;2,1220,9;1,1220,3;2748,1220;1,366;2750,1220;2730,1220
+
+>>> from math import nextafter
+>>> a1 = nextafter(-1.0, 10)
+>>> x = np.array([ a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, 0.3, 0.7, -0.2, -0.3,
+...                0.5, -0.3])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 6/16 items with area 10061706 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;6;2750;1220;2,1220,7;2750,1220;2730,1220;1,1220,5;1,366;2748,1220
+
+
+>>> x = np.array([ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                0.0, 0.0, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 5/16 items with area 9910948 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;5;2750;1220;2698,1;2750,1,12;2750,1216;2750,1215;2750,1160
+"""
+from math import isfinite
+from typing import Final
+
+import numpy as np
+from moptipy.api.encoding import Encoding
+from numpy.random import default_rng
+from pycommons.types import check_int_range, type_error
+
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.instgen.instance_space import InstanceSpace
+
+
+
+[docs] +class InstanceDecoder(Encoding): + """Decode a string of `n` real values in `[0,1]` to an instance.""" + + def __init__(self, space: InstanceSpace) -> None: + """ + Create the instance decoder. + + :param space: the instance description and space + """ + super().__init__() + if not isinstance(space, InstanceSpace): + raise type_error(space, "space", InstanceSpace) + #: the instance description + self.space: Final[InstanceSpace] = space + +
+[docs] + def get_x_dim(self, slack: float | int = 0) -> int: + """ + Get the minimum dimension that a real vector must have. + + :param slack: a parameter denoting the amount of slack for reducing + the item size + :return: the minimum dimension + """ + if not isinstance(slack, float | int): + raise type_error(slack, "slack", (float, int)) + if (not isfinite(slack)) or (slack < 0): + raise ValueError(f"slack={slack} is invalid") + base: Final[int] = check_int_range( + self.space.n_items - self.space.min_bins, "base", 1, 1_000_000) + added: Final[int] = int(slack * base + 0.5) + if (added < 0) or (added > 1_000_000): + raise ValueError(f"invalid result {added} based " + f"on slack={slack} and base={base}") + return 2 * (base + added)
+ + +
+[docs] + def decode(self, x: np.ndarray, y: list[Instance]) -> None: + """ + Decode the real-valued array to a 2D BPP instance. + + :param x: an array with values in [-1, 1] + :param y: the instance receiver + """ + # We start by using the prescribed number of bins as items. + # There will be n_bins items, each of which has exactly the size of a + # bin. Therefore, we know that all items fit exactly into n_bins bins. + bin_width: Final[int] = self.space.bin_width + bin_height: Final[int] = self.space.bin_height + n_bins: Final[int] = self.space.min_bins + items: list[list[int]] = [ + [bin_width, bin_height] for _ in range(n_bins)] + + # Now we need to make n_items - n_bins cuts. Each cut will divide one + # item into two items. Therefore, each cut yields exactly one new + # item. Therefore, after n_items - n_bins cuts we get n_items - n_bins + # new items. Since we start with n_bins items, this means that we get + # n_items - n_bins + n_bins items at the end, which are exactly + # n_items. + # + # Each cut cuts an item into two items along either the horizontal or + # vertical dimension. If we would put the two new, smaller items back + # together, they would exactly result in the item that we just cut. + # Therefore, they do fit into the same area and their area sum is also + # the same. Therefore, the overall area and the overall number of + # required bins will also stay the same. + x_idx: int = 0 + for cur_n_items in range(n_bins, self.space.n_items): + # In each iteration of this loop, we perform one cut. + # This means, that we add exactly one item to the item list. + # Our counter variable `cur_n_items`, which starts at `n_bins` + # and iterates until just before the target number of items + # `self.desc.n_items` represents the current number of items + # in our list `items`. + # It thus always holds that `cur_n_items == len(items)`. + + # Each cut is described by four properties: + # 1. The index of the item that we want to cut. + # 2. The direction (horizontal or vertical) into which we cut. + # 3. The location where we will cut along this dimension. + # 4. The direction into which we will search for the next item + # if the current item cannot be cut this way. + # These four properties are encoded in two real numbers `selector` + # and `cutter` from `[-1, 1]`. + + # The first number is `sel`. It encodes (1) and (4). + # First, we multiply `selector` with `cur_n_items` and take this + # modulo `cur_n_items`. This gives us a value `t` with + # `-cur_n_items < t < cur_n_items`. By adding `cur_n_items` + # and repeating the modulo division, we get + # `0 <= sel_i < cur_n_items`. + # `sel_i` is the index of the item that we want to cut. + selector: float = x[x_idx] + x_idx += 1 + + sel_i: int = ((int(cur_n_items * selector) % cur_n_items) + + cur_n_items) % cur_n_items + orig_sel_i: int = sel_i # the original selection index. + + # Now, it may not be possible to cut this item. + # The idea is that if we cannot cut the item, we simply try to cut + # the next item. + # The next item could be the one at the next-higher index or the + # one at the next lower index. + # If `selector < 0.0`, then we will next try the item at the next + # lower index. If `selector >= 0.0`, we will move to the next + # higher index. + # Of course, we will wrap our search when reaching either end of + # the list. + # + # If we arrive back at `orig_sel_i`, then we have tried to cut + # each item in the list in the prescribed cutting direction, + # however none could be cut. + # In this case, we will change the cutting direction. + # + # It must be possible to cut at least one item in at least one + # direction, or otherwise the problem would not be solvable. + # Therefore, we know that this way, trying all items in all + # directions in the worst case, we will definitely succeed. + sel_dir: int = -1 if selector < 0.0 else 1 + + # `cutter` tells us where to cut and in which direction. + # If `cutter >= 0`, then we will cut horizontally and + # if `cutter < 0`, we cut vertically. + # Each item is described by list `[width, height]`, so cutting + # horizontally means picking a vertical height coordinate and + # cutting horizontally along it. This means that for horizontal + # cuts, the `cut_dimension` should be `1` and for vertical cuts, + # it must be `0`. + cutter: float = x[x_idx] + x_idx += 1 + cut_dimension: int = 1 if cutter >= 0.0 else 0 + + while True: + cur_item: list[int] = items[sel_i] # Get the item to cut. + + item_size_in_dim: int = cur_item[cut_dimension] + + # We define the `cut_modulus` as the modulus for the cutting + # operation. We will definitely get a value for `cut_position` + # such that `0 < cut_position <= cut_modulus`. This means that + # we will get `0 < cut_position < item_size_in_dim`. + # Therefore, if `cut_modulus > 1`, then we know that there + # definitely is a `cut_position` at which we can cut the + # current item and obtain two new items that have both + # non-zero width and height. + cut_modulus: int = item_size_in_dim - 1 + if cut_modulus > 0: # Otherwise, we cannot cut the item. + cut_position: int = (((int( + cut_modulus * cutter) % cut_modulus) + cut_modulus) + % cut_modulus) + 1 + + if 0 < cut_position < item_size_in_dim: # Sanity check... + # Now we perform the actual cut. + # The original item now gets `cut_position` as the + # size in the `cut_dimension`, the other one gets + # `item_size_in_dim - cut_position`. Therefore, the + # overall size remains the same. + cur_item[cut_dimension] = cut_position + cur_item = cur_item.copy() + cur_item[cut_dimension] = ( + item_size_in_dim - cut_position) + items.append(cur_item) + break # we cut one item and can stop + + sel_i = ((((sel_i + sel_dir) % cur_n_items) + cur_n_items) + % cur_n_items) + if sel_i == orig_sel_i: + cut_dimension = 1 - cut_dimension + + # At this stage, our instance can only be solved with a perfect + # packing. The items need to be placed perfectly together and they + # will cover the complete `current_area`. + bin_area: Final[int] = bin_width * bin_height + current_area: int = n_bins * bin_area + + # However, requiring a perfect packing may add hardness to the problem + # that does not exist in the original problem. + # We now want to touch some of the items and make them a bit smaller. + # However, we must never make them smaller as + # `current_area - bin_area`, because then we could create a situation + # where less than `n_bins` bins are required. + # Also, we never want to slide under the `total_item_area` prescribed + # by the original problem. If the original problem prescribed a + # perfect packing, then we will create a perfect packing. + min_area: Final[int] = current_area - bin_area + 1 + + # If and only if the input array still has information left and if we + # still have area that we can cut, then let's continue. + # We will keep cutting items, but this time, we throw away the piece + # that we cut instead of adding it as item. Of course, we never cut + # more than what we are permitted to. + max_x_idx: Final[int] = len(x) + cur_n_items = list.__len__(items) + while (x_idx < max_x_idx) and (current_area > min_area): + # We perform the same selection and cutting choice. + selector = x[x_idx] + x_idx += 1 + sel_i = ((int(cur_n_items * selector) % cur_n_items) + + cur_n_items) % cur_n_items + orig_sel_i = sel_i # the original selection index. + sel_dir = -1 if selector < 0.0 else 1 + cutter = x[x_idx] + x_idx += 1 + cut_dimension = 1 if cutter >= 0.0 else 0 + + # We have selected the item and got the cutter value, too. + # Now we try to perform the cut. If we cannot cut, we try + # the next item. If we could not cut any item along the + # cut dimension, we switch the cut dimension. + # However, we may fail to cut anything (which means that + # we arrive at `step == 2`). In this case, we just skip + # this cut. + step: int = 0 + while step < 2: # This time, we may actually fail to cut. + cur_item = items[sel_i] # Get the item to cut. + + item_size_in_dim = cur_item[cut_dimension] + item_size_in_other_dim: int = cur_item[1 - cut_dimension] + # This time, the `cut_modulus` is also limited by the area + # that we can actually cut. This is `current_area - min_area`. + # Now each cut along the `cut_dimension` will cost us + # `item_size_in_other_dim` area units. + cut_modulus = min( + (current_area - min_area) // item_size_in_other_dim, + item_size_in_dim) - 1 + if cut_modulus > 0: + cut_position = (((int( + cut_modulus * cutter) % cut_modulus) + cut_modulus) + % cut_modulus) + 1 + + if 0 < cut_position < item_size_in_dim: + # We cut away cut_position and do not add the area at + # the end. + cur_item[cut_dimension] = \ + item_size_in_dim - cut_position + break # we cut one item and can stop + + sel_i = ((((sel_i + sel_dir) % cur_n_items) + cur_n_items) + % cur_n_items) + if sel_i == orig_sel_i: + cut_dimension = 1 - cut_dimension + step += 1 # If we tried everything, this enforces a stop. + + # Finally, we sort the items in order to merge items of the + # same dimension. + items.sort() + lo: int = 0 + n_items: int = list.__len__(items) + while lo < n_items: + # For each item in the list, we try to find all items of the same + # dimension. Since the list is sorted, these items must all be + # located together. + hi: int = lo + cur_item = items[lo] + while (hi < n_items) and (items[hi] == cur_item): + hi += 1 + cur_item.append(hi - lo) # We now have the item multiplicity. + # We delete all items with the same dimension (which must come + # directly afterward). + hi -= 1 + while lo < hi: + del items[hi] + hi -= 1 + n_items -= 1 + lo += 1 # Move to the next item + + # Now all items are sorted. This may or may not be a problem: + # Some heuristics may either benefit or suffer if the item list + # has a pre-defined structure. Therefore, we want to try to at least + # somewhat remove the order and make the item list order more random. + default_rng(int.from_bytes(x.tobytes())).shuffle(items) + + # And now we can fill in the result as the output of our encoding. + res: Instance = Instance( # Generate the instance + self.space.inst_name, bin_width, bin_height, items) + if list.__len__(y) > 0: # If the destination has length > 0... + y[0] = res # ...store the instance at index 0, + else: # otherwise + y.append(res) # add the instance to it.
+
+ +
diff --git a/_modules/moptipyapps/binpacking2d/instgen/instance_space.html b/_modules/moptipyapps/binpacking2d/instgen/instance_space.html new file mode 100644 index 00000000..c20800b4 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/instgen/instance_space.html @@ -0,0 +1,188 @@ +moptipyapps.binpacking2d.instgen.instance_space — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.instgen.instance_space

+"""An encoding that is inspired by a given instance."""
+from typing import Final
+
+from moptipy.api.space import Space
+from moptipy.utils.logger import KeyValueLogSection
+from pycommons.types import check_int_range, type_error
+
+from moptipyapps.binpacking2d.instance import IDX_HEIGHT, IDX_WIDTH, Instance
+
+
+
+[docs] +class InstanceSpace(Space): + """An space structure for the instance generation problem and space.""" + + def __init__(self, source: Instance) -> None: + """ + Create the space structure for the instance generation problem. + + :param source: the source instances whose date we take as an example + """ + super().__init__() + #: the instance name + self.inst_name: Final[str] = str.strip(source.name) + "n" + #: the target number of unique items + self.n_different_items: Final[int] = check_int_range( + source.n_different_items, "n_different_items", + 1, 100_000) + #: the target number of items (including repetitions) + self.n_items: Final[int] = check_int_range( + source.n_items, "n_items", source.n_different_items, + 1_000_000_000) + #: the bin width + self.bin_width: Final[int] = check_int_range( + source.bin_width, "bin_width", 1, 1_000_000_000) + #: the bin height + self.bin_height: Final[int] = check_int_range( + source.bin_height, "bin_height", 1, 1_000_000_000) + #: the minimum number of bins that this instance requires + self.min_bins: Final[int] = check_int_range( + min(source.lower_bound_bins, self.n_items), "min_bins", + 1, 1_000_000_000) + #: the minimum item width + self.item_width_min: Final[int] = check_int_range(int(min( + source[:, IDX_WIDTH])), "item_width_min", 1, self.bin_width) + #: the maximum item width + self.item_width_max: Final[int] = check_int_range(int(max( + source[:, IDX_WIDTH])), "item_width_max", self.item_width_min, + self.bin_width) + #: the minimum item height + self.item_height_min: Final[int] = check_int_range(int(min( + source[:, IDX_HEIGHT])), "item_height_min", 1, self.bin_height) + #: the maximum item height + self.item_height_max: Final[int] = check_int_range(int(max( + source[:, IDX_HEIGHT])), "item_height_max", self.item_height_min, + self.bin_height) + #: the total item area + self.total_item_area: Final[int] = check_int_range( + source.total_item_area, "total_item_area", 1, 1_000_000_000) + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log the parameters of this instance. + + :param logger: the logger + """ + super().log_parameters_to(logger) + logger.key_value("instName", self.inst_name) + logger.key_value("nItems", self.n_items) + logger.key_value("nDifferentItems", self.n_different_items) + logger.key_value("binWidth", self.bin_width) + logger.key_value("binHeight", self.bin_height) + logger.key_value("minBins", self.min_bins) + logger.key_value("itemWidthMin", self.item_width_min) + logger.key_value("itemWidthMax", self.item_width_max) + logger.key_value("itemHeightMin", self.item_height_min) + logger.key_value("itemHeightMax", self.item_height_max) + logger.key_value("totalItemArea", self.total_item_area)
+ + +
+[docs] + def create(self) -> list[Instance]: + """ + Generate a list for receiving an instance. + + :return: the new instance list + """ + return []
+ + +
+[docs] + def copy(self, dest: list[Instance], source: list[Instance]) -> None: + """ + Copy the instance list. + + :param dest: the destination instance list + :param source: the source instance list + """ + dest.clear() + dest.extend(source)
+ + +
+[docs] + def to_str(self, x: list[Instance]) -> str: # +book + """ + Convert an instance list to a string. + + :param x: the instance list + :return: the string representation of x + """ + return x[0].to_compact_str()
+ + +
+[docs] + def from_str(self, text: str) -> list[Instance]: + """ + Convert an instance string to a list with an instance. + + :param text: the input string + :return: the element in the space corresponding to `text` + """ + return [Instance.from_compact_str(text)]
+ + +
+[docs] + def is_equal(self, x1: list[Instance], x2: list[Instance]) -> bool: + """ + Check if the contents of two instances of the data structure are equal. + + :param x1: the first instance + :param x2: the second instance + :return: `True` if the contents are equal, `False` otherwise + """ + return x1 == x2
+ + +
+[docs] + def validate(self, x: list[Instance]) -> None: + """ + Check whether a given point in the space is valid. + + :param x: the point + :raises TypeError: if the point `x` (or one of its elements, if + applicable) has the wrong data type + :raises ValueError: if the point `x` is invalid and/or simply is not + an element of this space + """ + if list.__len__(x) != 1: + raise ValueError("There must be exactly one instance in x.") + inst: Instance = x[0] + if not isinstance(inst, Instance): + raise type_error(inst, "x[0]", Instance) + if inst.name != self.inst_name: + raise ValueError( + f"instance name {inst.name!r} != {self.inst_name!r}.") + if inst.bin_width != self.bin_width: + raise ValueError( + f"instance bin width={inst.bin_width} != {self.bin_width}.") + if inst.bin_height != self.bin_height: + raise ValueError( + "instance bin height=" + f"{inst.bin_height} != {self.bin_height}.") + if inst.n_items != self.n_items: + raise ValueError( + f"instance n_items={inst.n_items} != {self.n_items}.")
+ + +
+[docs] + def n_points(self) -> int: + """ + Get the approximate number of different elements in the space. + + :return: the approximate scale of the space + """ + return (self.bin_height * self.bin_width) ** self.n_items
+
+ +
diff --git a/_modules/moptipyapps/binpacking2d/instgen/problem.html b/_modules/moptipyapps/binpacking2d/instgen/problem.html new file mode 100644 index 00000000..08a3ea81 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/instgen/problem.html @@ -0,0 +1,46 @@ +moptipyapps.binpacking2d.instgen.problem — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.instgen.problem

+"""An instance of the instance generation problem."""
+
+from typing import Final
+
+from moptipy.api.component import Component
+from moptipy.spaces.vectorspace import VectorSpace
+
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.instgen.inst_decoding import InstanceDecoder
+from moptipyapps.binpacking2d.instgen.instance_space import InstanceSpace
+
+
+
+[docs] +class Problem(Component): + """An instance of the 2D Bin Packing Instance Generation Problem.""" + + def __init__(self, name: str, slack: float) -> None: + """ + Create an instance of the 2D bin packing instance generation problem. + + :param name: the name of the instance to select + :param slack: the additional fraction of slack + """ + super().__init__() + #: the instance to be used as template + self.solution_space: Final[InstanceSpace] = InstanceSpace( + Instance.from_resource(name)) + #: the internal decoding + self.encoding: Final[InstanceDecoder] = InstanceDecoder( + self.solution_space) + n: Final[int] = self.encoding.get_x_dim(slack) + #: the search space + self.search_space: Final[VectorSpace] = VectorSpace(n, -1.0, 1.0) + + def __str__(self) -> str: + """ + Get the string representation of this problem. + + :return: the string representation of this problem + """ + return (f"{self.solution_space.inst_name}_" + f"{self.search_space.dimension}")
+ +
diff --git a/_modules/moptipyapps/binpacking2d/make_instances.html b/_modules/moptipyapps/binpacking2d/make_instances.html new file mode 100644 index 00000000..0a29a7db --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/make_instances.html @@ -0,0 +1,241 @@ +moptipyapps.binpacking2d.make_instances — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.make_instances

+"""
+Obtain Instances of the 2D-dimensional Bin Packing Problem.
+
+With this program, we can obtain the instances of the two-dimensional bin
+packing problem and convert them to a singular resource file.
+The resource file holds one instance per line.
+
+1. Manuel Iori, Vinícius Loti de Lima, Silvano Martello, and Michele Monaci.
+   *2DPackLib*.
+   https://site.unibo.it/operations-research/en/research/2dpacklib
+2. Manuel Iori, Vinícius Loti de Lima, Silvano Martello, and Michele
+   Monaci. 2DPackLib: A Two-Dimensional Cutting and Packing Library.
+   *Optimization Letters* 16(2):471-480. March 2022.
+   https://doi.org/10.1007/s11590-021-01808-y
+"""
+
+import zipfile
+from io import BytesIO
+from math import isqrt
+from os.path import dirname, exists
+from typing import Any, Callable, Final, Iterable
+
+# noinspection PyPackageRequirements
+import certifi  # type: ignore
+
+# noinspection PyPackageRequirements
+import urllib3  # type: ignore
+from pycommons.io.path import Path, directory_path, file_path, write_lines
+from pycommons.io.temp import temp_dir
+from pycommons.types import type_error
+
+import moptipyapps.binpacking2d.instance as inst_mod
+from moptipyapps.binpacking2d.instance import (
+    INSTANCES_RESOURCE,
+    Instance,
+)
+
+#: the base url for 2DPackLib
+__BASE_URL: str = \
+    "https://site.unibo.it/operations-research/en/research/2dpacklib"
+
+#: The base URLs of the relevant 2DPackLib instances.
+__BASE_URLS: Final[Iterable[str]] = tuple(
+    f"{__BASE_URL}/{f}.zip" for f in ["a", "beng", "class"])
+
+
+
+[docs] +def download_2dpacklib_instances( + dest_dir: str, + source_urls: Iterable[str] = __BASE_URLS, + http: urllib3.PoolManager = urllib3.PoolManager( + cert_reqs="CERT_REQUIRED", ca_certs=certifi.where())) \ + -> Iterable[Path]: + """ + Download the instances from 2DPackLib to a folder. + + This function downloads the instances from 2DPackLib, which are provided + as zip archives containing one file per instance. It will extract all the + instances into the folder `dest_dir` and return a tuple of the extracted + files. You can specify the source URLs of the zip archives if you want, + but by default we use the three instance sets `A`, `BENG`, and `CLASS`. + + :param dest_dir: the destination directory + :param source_urls: the source URLs from which to download the zip + archives with the 2DPackLib-formatted instances + :param http: the HTTP pool + :return: the list of unpackaged files + """ + dest: Final[Path] = directory_path(dest_dir) + if not isinstance(source_urls, Iterable): + raise type_error(source_urls, "source_urls", Iterable) + result: Final[list[Path]] = [] + + for i, url in enumerate(source_urls): # iterate over the source URLs + if not isinstance(url, str): + raise type_error(url, f"source_urls[{i}]", str) + + response = http.request( # download the zip archive + "GET", url, redirect=True, retries=30) + with zipfile.ZipFile(BytesIO(response.data), mode="r") as z: + # unzip the ins2D files from the archives + files: Iterable[str] = [f for f in z.namelist() + if f.endswith(".ins2D")] + paths: list[Path] = [dest.resolve_inside(f) for f in files] + for path in paths: + if exists(path): + raise ValueError(f"file {path} already exists!") + z.extractall(dest, members=files) + for path in paths: + path.enforce_file() + result.append(path) + result.sort() + return tuple(result)
+ + + +def __normalize_2dpacklib_inst_name(instname: str) -> str: + """ + Normalize an instance name. + + :param instname: the name + :return: the normalized name + """ + if not isinstance(instname, str): + raise type_error(instname, "name", str) + instname = instname.strip().lower() + if (len(instname) == 2) and \ + (instname[0] in "abcdefghijklmnoprstuvwxyz") and \ + (instname[1] in "123456789"): + return f"{instname[0]}0{instname[1]}" + if instname.startswith("cl_"): + return f"cl{instname[3:]}" + return instname + + +
+[docs] +def append_almost_squares_strings(collector: Callable[[ + tuple[str, str]], Any]) -> None: + """ + Append the strings of the almost squares instances. + + :param collector: the instance collector + :return: the strings + """ + objects: list[list[int]] = [[2, 1, 1]] + size: int = 2 + for small_side in range(2, 36): + big_side = small_side + 1 + objects.append([big_side, small_side, 1]) + size += small_side * big_side + + bin_small = isqrt(size) + bin_big = bin_small + 1 + if (bin_big * bin_small) == size: + iname: str = str(small_side) + iname = f"asqas{iname}" if len(iname) >= 2 else f"asqas0{iname}" + collector((iname, Instance( + iname, bin_big, bin_small, objects).to_compact_str()))
+ + + +
+[docs] +def join_instances_to_compact( + binpacklib2d_files: Iterable[str], dest_file: str, + normalizer: Callable[[str], str] = __normalize_2dpacklib_inst_name) \ + -> tuple[Path, Iterable[str]]: + """ + Join all instances from a set of 2DPackLib files to one compact file. + + :param binpacklib2d_files: the iterable of 2DPackLib file paths + :param dest_file: the destination file + :param normalizer: the name normalizer, i.e., a function that processes + and/or transforms an instance name + :return: the canonical destination path and the list of instance names + stored + """ + if not isinstance(binpacklib2d_files, Iterable): + raise type_error(binpacklib2d_files, "files", Iterable) + if not callable(normalizer): + raise type_error(normalizer, "normalizer", call=True) + dest_path = Path(dest_file) + data: Final[list[tuple[str, str]]] = [] + for file in binpacklib2d_files: + inst: Instance = Instance.from_2dpacklib(file_path(file)) + inst.name = normalizer(inst.name) + data.append((inst.name, inst.to_compact_str())) + append_almost_squares_strings(data.append) # add the asquas instances + data.sort() + with dest_path.open_for_write() as wd: + write_lines((content for _, content in data), wd) + dest_path.enforce_file() + return dest_path, [thename for thename, _ in data]
+ + + +
+[docs] +def make_2dpacklib_resource( + dest_file: str | None = None, + source_urls: Iterable[str] = __BASE_URLS, + normalizer: Callable[[str], str] = __normalize_2dpacklib_inst_name)\ + -> tuple[Path, Iterable[str]]: + """ + Make the resource with all the relevant 2DPackLib instances. + + :param dest_file: the optional path to the destination file + :param source_urls: the source URLs from which to download the zip + archives with the 2DPackLib-formatted instances + :param normalizer: the name normalizer, i.e., a function that processes + and/or transforms an instance name + :return: the canonical path to the and the list of instance names stored + """ + dest_path: Final[Path] = directory_path(dirname(inst_mod.__file__))\ + .resolve_inside(INSTANCES_RESOURCE) if dest_file is None \ + else Path(dest_file) + with temp_dir() as temp: + files: Iterable[Path] = download_2dpacklib_instances( + dest_dir=temp, source_urls=source_urls) + return join_instances_to_compact( + files, dest_path, normalizer)
+ + + +# create the tables if this is the main script +if __name__ == "__main__": + _, names = make_2dpacklib_resource() + rows: list[str] = ["_INSTANCES: Final[tuple[str, ...]] = ("] + current = " " + has_space: bool = True + for name in names: + if (len(current) + (3 if has_space else 4) + len(name)) > 78: + rows.append(current) + current = " " + has_space = True + current = f'{current}"{name}",' if has_space else \ + f'{current} "{name}",' + has_space = False + rows.append(current[:-1] + ")") + + cmt_strs: list[str] = ( + "the the list of instance names of the 2DPackLib bin " + f"packing set downloaded from {__BASE_URL} ('a*'," + "'beng*', 'cl*') as well as the four non-trivial " + "'Almost Squares in Almost Squares' instances ('asqas*').").split() + cmt_str = "#:" + for word in cmt_strs: + if len(word) + 1 + len(cmt_str) <= 79: + cmt_str = f"{cmt_str} {word}" + else: + print(cmt_str) # noqa + cmt_str = f"#: {word}" + if len(cmt_str) > 2: + print(cmt_str) # noqa + + for s in rows: + print(s) # noqa +
diff --git a/_modules/moptipyapps/binpacking2d/objectives/.nojekyll b/_modules/moptipyapps/binpacking2d/objectives/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/moptipyapps/binpacking2d/objectives/bin_count.html b/_modules/moptipyapps/binpacking2d/objectives/bin_count.html new file mode 100644 index 00000000..73633c37 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/objectives/bin_count.html @@ -0,0 +1,141 @@ +moptipyapps.binpacking2d.objectives.bin_count — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.objectives.bin_count

+"""
+An objective function for minimizing the number of bins in packings.
+
+This function returns the number of bins.
+"""
+from typing import Final
+
+from moptipy.api.objective import Objective
+from pycommons.types import type_error
+
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.packing import IDX_BIN
+
+#: the name of the bin count objective function
+BIN_COUNT_NAME: Final[str] = "binCount"
+
+
+
+[docs] +class BinCount(Objective): + """Compute the number of bins.""" + + def __init__(self, instance: Instance) -> None: + """ + Initialize the number of bins objective function. + + :param instance: the instance to load the bounds from + """ + super().__init__() + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + #: the internal instance reference + self._instance: Final[Instance] = instance + +
+[docs] + def evaluate(self, x) -> int: + """ + Get the number of bins. + + :param x: the packing + :return: the number of bins used + """ + return int(x[:, IDX_BIN].max())
+ + +
+[docs] + def lower_bound(self) -> int: + """ + Get the lower bound of the number of bins objective. + + :return: the lower bound for the number of required bins, i.e., + :attr:`~moptipyapps.binpacking2d.instance.Instance.\ +lower_bound_bins` + + >>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]]) + >>> ins.lower_bound_bins + 1 + >>> BinCount(ins).lower_bound() + 1 + + >>> ins = Instance("b", 10, 50, [[10, 5, 10], [3, 3, 1], [5, 5, 1]]) + >>> ins.lower_bound_bins + 2 + >>> BinCount(ins).lower_bound() + 2 + + >>> ins = Instance("c", 10, 50, [[10, 5, 20], [30, 3, 10], [5, 5, 1]]) + >>> ins.lower_bound_bins + 4 + >>> BinCount(ins).lower_bound() + 4 + """ + return self._instance.lower_bound_bins
+ + +
+[docs] + def is_always_integer(self) -> bool: + """ + Return `True` because there are only integer bins. + + :retval True: always + """ + return True
+ + +
+[docs] + def upper_bound(self) -> int: + """ + Get the upper bound of the number of bins. + + :return: the number of items in the instance, i.e., + :attr:`~moptipyapps.binpacking2d.instance.Instance.n_items` + + >>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]]) + >>> ins.n_items + 3 + >>> BinCount(ins).upper_bound() + 3 + + >>> ins = Instance("b", 10, 50, [[10, 5, 10], [3, 3, 1], [5, 5, 1]]) + >>> ins.n_items + 12 + >>> BinCount(ins).upper_bound() + 12 + + >>> ins = Instance("c", 10, 50, [[10, 5, 20], [30, 3, 10], [5, 5, 1]]) + >>> ins.n_items + 31 + >>> BinCount(ins).upper_bound() + 31 + """ + return self._instance.n_items
+ + +
+[docs] + def to_bin_count(self, z: int) -> int: + """ + Get the bin count corresponding to an objective value. + + :param z: + :return: the value itself + """ + return z
+ + + def __str__(self) -> str: + """ + Get the name of the bins objective function. + + :return: `binCount` + :retval "binCount": always + """ + return BIN_COUNT_NAME
+ +
diff --git a/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_empty.html b/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_empty.html new file mode 100644 index 00000000..bbdb8c7a --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_empty.html @@ -0,0 +1,111 @@ +moptipyapps.binpacking2d.objectives.bin_count_and_empty — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.objectives.bin_count_and_empty

+"""
+An objective function indirectly minimizing the number of bins in packings.
+
+This objective function first computes the number of bins used. Let's call it
+`n_bins`. We know the total number of items,
+:attr:`~moptipyapps.binpacking2d.instance.Instance.n_items`, as
+well (because this is also the number of rows in the packing).
+Now we return `(n_items * (n_bins - 1)) + min_items`,
+where `min_items` is the number of items in the bin with the fewest items.
+
+This is similar to :mod:`~moptipyapps.binpacking2d.objectives.\
+bin_count_and_last_small`, but instead of focussing on the very last bin, it
+uses the minimum element count over all bins. It has the same lower- and upper
+bound, though.
+"""
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.objectives.bin_count_and_last_empty import (
+    BinCountAndLastEmpty,
+)
+from moptipyapps.binpacking2d.packing import IDX_BIN
+
+
+
+[docs] +@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) +def bin_count_and_empty(y: np.ndarray, temp: np.ndarray) -> int: + """ + Get the number of bins and number of elements in the emptiest one. + + We compute the total number of bins minus 1 and multiply it with the + number of items. We then add the smallest number of elements in any + bin. + + :param y: the packing + :param temp: the temporary array + :return: the objective value + + >>> tempa = np.empty(10, int) + >>> bin_count_and_empty(np.array([[1, 1, 10, 10, 20, 20], + ... [1, 1, 30, 30, 40, 40], + ... [1, 1, 20, 20, 30, 30]], int), tempa) + 3 + >>> bin_count_and_empty(np.array([[1, 1, 10, 10, 20, 20], + ... [1, 2, 30, 30, 40, 40], # bin 2! + ... [1, 1, 20, 20, 30, 30]], int), tempa) + 4 + >>> bin_count_and_empty(np.array([[1, 2, 10, 10, 20, 20], # bin 2! + ... [1, 2, 30, 30, 40, 40], # bin 2! + ... [1, 1, 20, 20, 30, 30]], int), tempa) + 4 + >>> bin_count_and_empty(np.array([[1, 3, 10, 10, 20, 20], # bin 3! + ... [1, 2, 30, 30, 40, 40], # bin 2! + ... [1, 1, 20, 20, 30, 30]], int), tempa) + 7 + """ + total_bins: int = -1 # the current idea of what the last bin is + temp.fill(0) # empty all temporary values + n_items: Final[int] = len(y) # the number of rows in the matrix + + for i in range(n_items): # iterate over all packed items + bin_idx: int = int(y[i, IDX_BIN]) - 1 # get the bin index of the item + temp[bin_idx] += 1 # increase number of items + total_bins = max(total_bins, bin_idx) + return (n_items * total_bins) + temp[0:total_bins + 1].min()
+ + + +
+[docs] +class BinCountAndEmpty(BinCountAndLastEmpty): + """Get the number of bins and number of elements in the emptiest one.""" + + def __init__(self, instance: Instance) -> None: + """ + Initialize the objective function. + + :param instance: the instance to load the bounds from + """ + super().__init__(instance) + #: the internal temporary array + self.__temp: Final[np.ndarray] = np.empty( + instance.n_items, instance.dtype) + +
+[docs] + def evaluate(self, x) -> int: + """ + Evaluate the objective function. + + :param x: the solution + :return: the bin size and smallest-bin-area factor + """ + return bin_count_and_empty(x, self.__temp)
+ + + def __str__(self) -> str: + """ + Get the name of the bins objective function. + + :return: `binCountAndEmpty` + :retval "binCountAndEmpty": always + """ + return "binCountAndEmpty"
+ +
diff --git a/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_last_empty.html b/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_last_empty.html new file mode 100644 index 00000000..0ce91335 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_last_empty.html @@ -0,0 +1,196 @@ +moptipyapps.binpacking2d.objectives.bin_count_and_last_empty — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.objectives.bin_count_and_last_empty

+"""
+An objective function indirectly minimizing the number of bins in packings.
+
+This objective function first computes the number of bins used. Let's call it
+`n_bins`. We know the total number of items,
+:attr:`~moptipyapps.binpacking2d.instance.Instance.n_items`, as
+well (because this is also the number of rows in the packing).
+Now we return `(n_items * (n_bins - 1)) + number_of_items_in_last_bin`,
+where `number_of_items_in_last_bin` is, well, the number of items in the
+very last bin.
+
+The idea behind this is: If one of two packings has the smaller number of
+bins, then this one will always have the smaller objective value. If two
+packings have the same number of bins, but one has fewer items in the very
+last bin, then that one is better. With this mechanism, we drive the search
+towards "emptying" the last bin. If the number of items in the last bin would
+reach `0`, that last bin would disappear - and we have one bin less.
+"""
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+from pycommons.math.int_math import ceil_div
+
+from moptipyapps.binpacking2d.objectives.bin_count import BinCount
+from moptipyapps.binpacking2d.packing import IDX_BIN
+
+
+
+[docs] +@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) +def bin_count_and_last_empty(y: np.ndarray) -> int: + """ + Compute the number of bins and the emptiness of the last bin. + + We compute the total number of bins minus 1 and multiply it with the + number of items. We then add the number of items in the last bin. + + :param y: the packing + :return: the objective value + + >>> bin_count_and_last_empty(np.array([[1, 1, 10, 10, 20, 20], + ... [1, 1, 30, 30, 40, 40], + ... [1, 1, 20, 20, 30, 30]], int)) + 3 + >>> bin_count_and_last_empty(np.array([[1, 1, 10, 10, 20, 20], + ... [1, 2, 30, 30, 40, 40], # bin 2! + ... [1, 1, 20, 20, 30, 30]], int)) + 4 + >>> bin_count_and_last_empty(np.array([[1, 2, 10, 10, 20, 20], # bin 2! + ... [1, 2, 30, 30, 40, 40], # bin 2! + ... [1, 1, 20, 20, 30, 30]], int)) + 5 + >>> bin_count_and_last_empty(np.array([[1, 3, 10, 10, 20, 20], # bin 3! + ... [1, 2, 30, 30, 40, 40], # bin 2! + ... [1, 1, 20, 20, 30, 30]], int)) + 7 + """ + current_bin: int = -1 # the current idea of what the last bin is + current_size: int = -1 # the number of items already in that bin + n_items: Final[int] = len(y) # the number of rows in the matrix + + for i in range(n_items): # iterate over all packed items + bin_idx: int = int(y[i, IDX_BIN]) # get the bin index of the item + if bin_idx > current_bin: # it's a new biggest bin = new last bin? + current_size = 1 # then there is 1 object in it for now + current_bin = bin_idx # and we remember it + elif bin_idx == current_bin: # did item go into the current last bin? + current_size = current_size + 1 # then increase size + return (n_items * (current_bin - 1)) + current_size # return objective
+ + + +
+[docs] +class BinCountAndLastEmpty(BinCount): + """Compute the number of bins and the emptiness of the last one.""" + +
+[docs] + def evaluate(self, x) -> int: + """ + Evaluate the objective function. + + :param x: the solution + :return: the bin size and emptyness factor + """ + return bin_count_and_last_empty(x)
+ + +
+[docs] + def lower_bound(self) -> int: + """ + Get the lower bound of the number of bins and emptiness objective. + + We know from the instance (:attr:`~moptipyapps.binpacking2d\ +.instance.Instance.lower_bound_bins`) that we require at least as many bins + such that they can accommodate the total area of all items together. + Let's call this number `lb`. Now if `lb` is one, then all objects could + be in the first bin, in which case the objective value would equal to + :attr:`~moptipyapps.binpacking2d.instance.Instance.n_items`, + i.e., the total number of items in the first = last bin. + If it is `lb=2`, then we know that we will need at least two bins. The + best case would be that `n_items - 1` items are in the first bin and + one is in the last bin. This means that we would get `1 * n_items + 1` + as objective value. If we have `lb=3` bins, then we could have + `n_items - 1` items distributed over the first two bins with one item + left over in the last bin, i.e., would get `(2 * n_items) + 1`. And so + on. + + :return: `max(n_items, (lb - 1) * n_items + 1)` + + >>> from moptipyapps.binpacking2d.instance import Instance + >>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]]) + >>> ins.n_items + 3 + >>> ins.lower_bound_bins + 1 + >>> BinCountAndLastEmpty(ins).lower_bound() + 3 + + >>> ins = Instance("b", 10, 50, [[10, 5, 10], [3, 3, 1], [5, 5, 1]]) + >>> ins.n_items + 12 + >>> ins.lower_bound_bins + 2 + >>> BinCountAndLastEmpty(ins).lower_bound() + 13 + + >>> ins = Instance("c", 10, 50, [[10, 5, 20], [30, 3, 10], [5, 5, 1]]) + >>> ins.n_items + 31 + >>> ins.lower_bound_bins + 4 + >>> BinCountAndLastEmpty(ins).lower_bound() + 94 + """ + return max(self._instance.n_items, + ((self._instance.lower_bound_bins - 1) + * self._instance.n_items) + 1)
+ + +
+[docs] + def upper_bound(self) -> int: + """ + Get the upper bound of the number of bins plus emptiness. + + :return: the number of items in the instance to the square + + >>> from moptipyapps.binpacking2d.instance import Instance + >>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]]) + >>> ins.n_items + 3 + >>> BinCountAndLastEmpty(ins).upper_bound() + 9 + + >>> ins = Instance("b", 10, 50, [[10, 5, 10], [3, 3, 1], [5, 5, 1]]) + >>> ins.n_items + 12 + >>> BinCountAndLastEmpty(ins).upper_bound() + 144 + + >>> ins = Instance("c", 10, 50, [[10, 5, 20], [30, 3, 10], [5, 5, 1]]) + >>> ins.n_items + 31 + >>> BinCountAndLastEmpty(ins).upper_bound() + 961 + """ + return self._instance.n_items * self._instance.n_items
+ + +
+[docs] + def to_bin_count(self, z: int) -> int: + """ + Convert an objective value to a bin count. + + :param z: the objective value + :return: the bin count + """ + return ceil_div(z, self._instance.n_items)
+ + + def __str__(self) -> str: + """ + Get the name of the bins objective function. + + :return: `binCountAndLastEmpty` + :retval "binCountAndLastEmpty": always + """ + return "binCountAndLastEmpty"
+ +
diff --git a/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_last_skyline.html b/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_last_skyline.html new file mode 100644 index 00000000..0cf2137c --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_last_skyline.html @@ -0,0 +1,190 @@ +moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline

+"""
+An objective function indirectly minimizing the number of bins in packings.
+
+This objective function minimizes the number of bins and maximizes the
+"usable" space in the last bin.
+
+Which space is actually useful for our encodings? Let's say we have filled
+a bin to a certain degree and somewhere there is a "hole" in the filled area,
+but this hole is covered by another object. The area of the hole is not used,
+but it also cannot be used anymore. The area that we can definitely use is the
+area above the "skyline" of the objects in the bin. The skyline at any
+horizontal `x` coordinate be the highest border of any object that intersects
+with `x` horizontally. In other words, it is the `y` value at and above which
+no other object is located at this `x` coordinate. The area below the skyline
+cannot be used anymore. The area above the skyline can.
+
+If we minimize the area below the skyline in the very last bin, then this will
+a similar impact as minimizing the overall object area in the last bin (see
+:mod:`~moptipyapps.binpacking2d.objectives.bin_count_and_last_small`). We push
+the skyline lower and lower and, if we are lucky, the last bin eventually
+becomes empty.
+
+The objective
+:mod:`~moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline`
+works quite similarly to this one, but minimizes the lowest skyline over any
+bin.
+"""
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.objectives.bin_count_and_last_small import (
+    BinCountAndLastSmall,
+)
+from moptipyapps.binpacking2d.packing import (
+    IDX_BIN,
+    IDX_LEFT_X,
+    IDX_RIGHT_X,
+    IDX_TOP_Y,
+)
+
+
+
+[docs] +@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) +def bin_count_and_last_skyline(y: np.ndarray, bin_width: int, + bin_height: int) -> int: + """ + Compute the bin count-1 times the bin size + the space below the skyline. + + :param y: the packing + :param bin_width: the bin width + :param bin_height: the bin height + :return: the objective value + + >>> 10*0 + 10*20 + 10*30 + 10*40 + 10*0 + 900 + >>> bin_count_and_last_skyline(np.array([[1, 1, 10, 10, 20, 20], + ... [1, 1, 30, 30, 40, 40], + ... [1, 1, 20, 20, 30, 30]], int), + ... 50, 50) + 900 + >>> 5 * 0 + 5 * 10 + 10 * 20 + 5 * 10 + 25 * 0 + 300 + >>> bin_count_and_last_skyline(np.array([[1, 1, 5, 0, 15, 10], + ... [1, 1, 10, 10, 20, 20], + ... [1, 1, 15, 0, 25, 10]], int), + ... 50, 50) + 300 + >>> 50*50 + 0*10 + 10*20 + 30*0 + 2700 + >>> bin_count_and_last_skyline(np.array([[1, 1, 5, 0, 15, 10], + ... [1, 2, 10, 10, 20, 20], + ... [1, 1, 15, 0, 25, 10]], int), + ... 50, 50) + 2700 + >>> 5 * 0 + 5 * 10 + 3 * 20 + (50 - 13) * 25 + 1035 + >>> bin_count_and_last_skyline(np.array([[1, 1, 5, 0, 15, 10], + ... [1, 1, 10, 10, 20, 20], + ... [1, 1, 15, 0, 25, 10], + ... [2, 1, 13, 20, 50, 25]], int), + ... 50, 50) + 1035 + >>> 50*50*3 + 25*50 + 8750 + >>> bin_count_and_last_skyline(np.array([[1, 1, 0, 0, 10, 10], + ... [2, 2, 0, 0, 20, 20], + ... [3, 3, 0, 0, 25, 10], + ... [4, 4, 0, 0, 50, 25]], int), + ... 50, 50) + 8750 + >>> 50*50*3 + 25*10 + 25*0 + 7750 + >>> bin_count_and_last_skyline(np.array([[1, 1, 0, 0, 10, 10], + ... [2, 2, 0, 0, 20, 20], + ... [3, 4, 0, 0, 25, 10], + ... [4, 3, 0, 0, 50, 25]], int), + ... 50, 50) + 7750 + >>> 50*50*3 + 20*20 + 30*0 + 7900 + >>> bin_count_and_last_skyline(np.array([[1, 1, 0, 0, 10, 10], + ... [2, 4, 0, 0, 20, 20], + ... [3, 2, 0, 0, 25, 10], + ... [4, 3, 0, 0, 50, 25]], int), + ... 50, 50) + 7900 + >>> 50*50*3 + 20*10 + 30*20 + 20*0 + 8300 + >>> bin_count_and_last_skyline(np.array([[1, 1, 0, 0, 10, 10], + ... [2, 4, 0, 0, 20, 20], + ... [3, 2, 0, 0, 25, 10], + ... [2, 4, 10, 20, 30, 30], + ... [4, 3, 0, 0, 50, 25]], int), + ... 50, 50) + 8300 + """ + bins: Final[int] = int(y[:, IDX_BIN].max()) + len_y: Final[int] = len(y) + bin_size: Final[int] = bin_height * bin_width + area_under_skyline: int = 0 + + cur_left: int = 0 + use_bin: int = max(y[:, IDX_BIN]) # the bin to use + while cur_left < bin_width: + use_right = next_left = bin_width + use_top: int = 0 + for i in range(len_y): + if y[i, IDX_BIN] != use_bin: + continue + left: int = int(y[i, IDX_LEFT_X]) + right: int = int(y[i, IDX_RIGHT_X]) + top: int = int(y[i, IDX_TOP_Y]) + if left <= cur_left < right and top > use_top: + use_top = top + use_right = right + if cur_left < left < next_left: + next_left = left + + use_right = min(use_right, next_left) + area_under_skyline += (use_right - cur_left) * use_top + cur_left = use_right + return ((bins - 1) * bin_size) + area_under_skyline
+ + + +
+[docs] +class BinCountAndLastSkyline(BinCountAndLastSmall): + """Compute the number of bins and the useful area in the last bin.""" + + def __init__(self, instance: Instance) -> None: + """ + Initialize the objective function. + + :param instance: the instance to load the bounds from + """ + super().__init__(instance) + #: the bin width + self.__bin_width: Final[int] = instance.bin_width + #: the bin height + self.__bin_height: Final[int] = instance.bin_height + +
+[docs] + def evaluate(self, x) -> int: + """ + Evaluate the objective function. + + :param x: the solution + :return: the bin size and last-bin-small-area factor + """ + return bin_count_and_last_skyline( + x, self.__bin_width, self.__bin_height)
+ + + def __str__(self) -> str: + """ + Get the name of the bins objective function. + + :return: `binCountAndLowestSkyline` + :retval "binCountAndLowestSkyline": always + """ + return "binCountAndLastSkyline"
+ +
diff --git a/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_last_small.html b/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_last_small.html new file mode 100644 index 00000000..61855a5d --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_last_small.html @@ -0,0 +1,240 @@ +moptipyapps.binpacking2d.objectives.bin_count_and_last_small — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.objectives.bin_count_and_last_small

+"""
+An objective function indirectly minimizing the number of bins in packings.
+
+This objective function computes the number of bins used. Let's call it
+`n_bins`. We know the area `bin_area` of a bin as well. Now we return
+`(bin_area * (n_bins - 1)) + area_of_items_in_last_bin`, where
+`area_of_items_in_last_bin` is, well, the area covered by items in the
+very last bin.
+
+The idea behind this is: If one of two packings has the smaller number of
+bins, then this one will always have the smaller objective value. If two
+packings have the same number of bins, but one requires less space in the very
+last bin, then that one is better. With this mechanism, we drive the search
+towards "emptying" the last bin. If the number of items in the last bin would
+reach `0`, that last bin would disappear - and we have one bin less.
+
+This objective is similar to :mod:`~moptipyapps.binpacking2d.objectives.\
+bin_count_and_small`, with the difference that it focuses on the *last* bin
+whereas :mod:`~moptipyapps.binpacking2d.objectives.bin_count_and_small` tries
+to minimize the area in *any* bin.
+"""
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+from pycommons.math.int_math import ceil_div
+
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.objectives.bin_count import BinCount
+from moptipyapps.binpacking2d.packing import (
+    IDX_BIN,
+    IDX_BOTTOM_Y,
+    IDX_LEFT_X,
+    IDX_RIGHT_X,
+    IDX_TOP_Y,
+)
+
+
+
+[docs] +@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) +def bin_count_and_last_small(y: np.ndarray, bin_area: int) -> int: + """ + Compute the number of bins and the occupied area in the last bin. + + We compute the total number of bins minus 1 and multiply it with the + total area of items. We then add the area of items in the last bin. + + :param y: the packing + :param bin_area: the area of a single bin + :return: the objective value + + >>> bin_count_and_last_small(np.array([[1, 1, 10, 10, 20, 20], + ... [1, 1, 30, 30, 40, 40], + ... [1, 1, 20, 20, 30, 30]], int), + ... 50*50) + 300 + >>> bin_count_and_last_small(np.array([[1, 1, 10, 10, 20, 20], + ... [1, 2, 30, 30, 40, 40], # bin 2! + ... [1, 1, 20, 20, 30, 30]], int), + ... 50*50) + 2600 + >>> bin_count_and_last_small(np.array([[1, 2, 10, 10, 20, 20], # bin 2! + ... [1, 2, 30, 30, 40, 40], # bin 2! + ... [1, 1, 20, 20, 30, 30]], int), + ... 50*50) + 2700 + >>> bin_count_and_last_small(np.array([[1, 3, 10, 10, 20, 20], # bin 3! + ... [1, 2, 30, 30, 40, 40], # bin 2! + ... [1, 1, 20, 20, 30, 30]], int), + ... 50*50) + 5100 + >>> bin_count_and_last_small(np.array([[1, 3, 10, 10, 50, 50], # bin 3! + ... [1, 2, 30, 30, 60, 60], # bin 2! + ... [1, 1, 20, 20, 30, 30]], np.int8), + ... 50*50) + 6600 + """ + current_bin: int = -1 # the current idea of what the last bin is + current_area: int = 0 # the area of items already in that bin + n_items: Final[int] = len(y) # the number of rows in the matrix + + for i in range(n_items): # iterate over all packed items + bin_idx: int = int(y[i, IDX_BIN]) # get the bin index of the item + if bin_idx < current_bin: + continue + area: int = int(y[i, IDX_RIGHT_X] - y[i, IDX_LEFT_X]) \ + * int(y[i, IDX_TOP_Y] - y[i, IDX_BOTTOM_Y]) + if bin_idx > current_bin: # it's a new biggest bin = new last bin? + current_area = area # then the current area is this + current_bin = bin_idx # and we remember it + elif bin_idx == current_bin: # did item go into the current last bin? + current_area += area # then increase size + return (bin_area * (current_bin - 1)) + current_area # return objective
+ + + +
+[docs] +class BinCountAndLastSmall(BinCount): + """Compute the number of bins and the area in the last one.""" + + def __init__(self, instance: Instance) -> None: + """ + Initialize the objective function. + + :param instance: the instance to load the bounds from + """ + super().__init__(instance) + #: the bin size + self._bin_size: Final[int] = instance.bin_width * instance.bin_height + +
+[docs] + def evaluate(self, x) -> int: + """ + Evaluate the objective function. + + :param x: the solution + :return: the bin size and last-bin-small-area factor + """ + return bin_count_and_last_small(x, self._bin_size)
+ + +
+[docs] + def to_bin_count(self, z: int) -> int: + """ + Convert an objective value to a bin count. + + :param z: the objective value + :return: the bin count + """ + return ceil_div(z, self._bin_size)
+ + +
+[docs] + def lower_bound(self) -> int: + """ + Get the lower bound of the number of bins and small-size objective. + + We know from the instance (:attr:`~moptipyapps.binpacking2d\ +.instance.Instance.lower_bound_bins`) that we require at least as many bins + such that they can accommodate the total area of all items together. + Let's call this number `lb`. Now if `lb` is one, then all objects could + be in the first bin, in which case the objective value would equal to + the total area of all items (:attr:`~moptipyapps.binpacking2d\ +.instance.Instance.total_item_area`). + If it is `lb=2`, then we know that we will need at least two bins. The + best case would be that almost all items are in the first bin and + only the smallest object is in the last bin. This means that we would + get `1 * bin_area + smallest_area` as objective value. If we have + `lb=3` bins, then we could again have all but the smallest items + distributed over the first two bins and only the smallest one in the + last bin, i.e., would get `(2 * bin_area) + smallest_area`. And so on. + + :return: `total_item_area` if the lower bound `lb` of the number of + bins is `1`, else `(lb - 1) * bin_area + smallest_area`, where + `bin_area` is the area of a bin, `total_item_area` is the area of + all items added up, and `smallest_area` is the area of the + smallest item + + >>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]]) + >>> ins.total_item_area + 84 + >>> ins.lower_bound_bins + 1 + >>> BinCountAndLastSmall(ins).lower_bound() + 84 + + >>> ins = Instance("b", 10, 50, [[10, 5, 10], [3, 3, 1], [5, 5, 1]]) + >>> ins.total_item_area + 534 + >>> ins.lower_bound_bins + 2 + >>> BinCountAndLastSmall(ins).lower_bound() + 509 + + >>> ins = Instance("c", 10, 50, [[10, 5, 10], [30, 3, 1], [5, 5, 1]]) + >>> ins.total_item_area + 615 + >>> ins.lower_bound_bins + 2 + >>> BinCountAndLastSmall(ins).lower_bound() + 525 + """ + if self._instance.lower_bound_bins == 1: + return self._instance.total_item_area + smallest_area: int = -1 + for row in self._instance: + area: int = int(row[0]) * int(row[1]) + if (smallest_area < 0) or (area < smallest_area): + smallest_area = area + return int(((self._instance.lower_bound_bins - 1) + * self._instance.bin_height + * self._instance.bin_width) + smallest_area)
+ + +
+[docs] + def upper_bound(self) -> int: + """ + Get the upper bound of this objective function. + + :return: a very coarse estimate of the upper bound + + >>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]]) + >>> ins.n_items + 3 + >>> BinCountAndLastSmall(ins).upper_bound() + 15000 + + >>> ins = Instance("b", 10, 50, [[10, 5, 10], [3, 3, 1], [5, 5, 1]]) + >>> ins.n_items + 12 + >>> BinCountAndLastSmall(ins).upper_bound() + 6000 + + >>> ins = Instance("c", 10, 50, [[10, 5, 10], [30, 3, 1], [5, 5, 10]]) + >>> ins.n_items + 21 + >>> BinCountAndLastSmall(ins).upper_bound() + 10500 + """ + return self._instance.n_items * self._instance.bin_height \ + * self._instance.bin_width
+ + + def __str__(self) -> str: + """ + Get the name of the bins objective function. + + :return: `binCountAndLastSmall` + :retval "binCountAndLastSmall": always + """ + return "binCountAndLastSmall"
+ +
diff --git a/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_lowest_skyline.html b/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_lowest_skyline.html new file mode 100644 index 00000000..3cbaf982 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_lowest_skyline.html @@ -0,0 +1,176 @@ +moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline

+"""
+An objective function indirectly minimizing the number of bins in packings.
+
+This objective function minimizes the number of bins and maximizes the
+"useable" space in any bin.
+
+Which space is actually useful for our encodings? Let's say we have filled
+a bin to a certain degree and somewhere there is a "hole" in the filled area,
+but this hole is covered by another object. The area of the hole is not used,
+but it also cannot be used anymore. The area that we can definitely use is the
+area above the "skyline" of the objects in the bin. The skyline at any
+horizontal `x` coordinate be the highest border of any object that intersects
+with `x` horizontally. In other words, it is the `y` value at and above which
+no other object is located at this `x` coordinate. The area below the skyline
+cannot be used anymore. The area above the skyline can.
+
+If we minimize the area below the skyline in the very last bin, then this will
+a similar impact as minimizing the overall object area in the last bin (see
+:mod:`~moptipyapps.binpacking2d.objectives.bin_count_and_last_small`). We push
+the skyline lower and lower and, if we are lucky, the last bin eventually
+becomes empty. This is done with the objective
+:mod:`~moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline`.
+
+But we could also minimize the area under the skylines in the other bins.
+Because a) if we can get any skyline in any bin to become 0, then this bin
+disappears and b) if we can free up space in the bins by lowering the
+skylines, then we have a better chance to move an object from the *next* bin
+forward into that bin, which increases the chance to make that bin empty.
+
+In this objective function, we therefore use the smallest skyline area over
+all bins to distinguish between packings of the same number of bins.
+For all intents and purposes, it has the same lower and upper bound as the
+:mod:`~moptipyapps.binpacking2d.objectives.bin_count_and_last_small`
+objective.
+"""
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.objectives.bin_count_and_last_small import (
+    BinCountAndLastSmall,
+)
+from moptipyapps.binpacking2d.packing import (
+    IDX_BIN,
+    IDX_LEFT_X,
+    IDX_RIGHT_X,
+    IDX_TOP_Y,
+)
+
+
+
+[docs] +@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) +def bin_count_and_lowest_skyline(y: np.ndarray, bin_width: int, + bin_height: int) -> int: + """ + Compute the bin count-1 times the bin size + the space below the skyline. + + :param y: the packing + :param bin_width: the bin width + :param bin_height: the bin height + :return: the objective value + + >>> 10*0 + 10*20 + 10*30 + 10*40 + 10*0 + 900 + >>> bin_count_and_lowest_skyline(np.array([[1, 1, 10, 10, 20, 20], + ... [1, 1, 30, 30, 40, 40], + ... [1, 1, 20, 20, 30, 30]], int), + ... 50, 50) + 900 + >>> 5 * 0 + 5 * 10 + 10 * 20 + 5 * 10 + 25 * 0 + 300 + >>> bin_count_and_lowest_skyline(np.array([[1, 1, 5, 0, 15, 10], + ... [1, 1, 10, 10, 20, 20], + ... [1, 1, 15, 0, 25, 10]], int), + ... 50, 50) + 300 + >>> 50*50 + min(5*0 + 10*10 + 10*10 + 25*0, 10*0 + 10*20 + 30*0) + 2700 + >>> bin_count_and_lowest_skyline(np.array([[1, 1, 5, 0, 15, 10], + ... [1, 2, 10, 10, 20, 20], + ... [1, 1, 15, 0, 25, 10]], int), + ... 50, 50) + 2700 + >>> 5 * 0 + 5 * 10 + 3 * 20 + (50 - 13) * 25 + 1035 + >>> bin_count_and_lowest_skyline(np.array([[1, 1, 5, 0, 15, 10], + ... [1, 1, 10, 10, 20, 20], + ... [1, 1, 15, 0, 25, 10], + ... [2, 1, 13, 20, 50, 25]], int), + ... 50, 50) + 1035 + >>> 2500*3 + min(10*10, 20*20, 25*10, 50*25) + 7600 + >>> bin_count_and_lowest_skyline(np.array([[1, 1, 0, 0, 10, 10], + ... [2, 2, 0, 0, 20, 20], + ... [3, 3, 0, 0, 25, 10], + ... [4, 4, 0, 0, 50, 25]], int), + ... 50, 50) + 7600 + """ + bins: Final[int] = int(y[:, IDX_BIN].max()) + len_y: Final[int] = len(y) + bin_size: Final[int] = bin_height * bin_width + min_area_under_skyline: int = bin_size + + for use_bin in range(1, bins + 1): + cur_left: int = 0 + area_under_skyline: int = 0 + while cur_left < bin_width: + use_right = next_left = bin_width + use_top: int = 0 + for i in range(len_y): + if y[i, IDX_BIN] != use_bin: + continue + left: int = int(y[i, IDX_LEFT_X]) + right: int = int(y[i, IDX_RIGHT_X]) + top: int = int(y[i, IDX_TOP_Y]) + if left <= cur_left < right and top > use_top: + use_top = top + use_right = right + if cur_left < left < next_left: + next_left = left + + use_right = min(use_right, next_left) + area_under_skyline += (use_right - cur_left) * use_top + cur_left = use_right + min_area_under_skyline = min(min_area_under_skyline, + area_under_skyline) + return ((bins - 1) * bin_size) + min_area_under_skyline
+ + + +
+[docs] +class BinCountAndLowestSkyline(BinCountAndLastSmall): + """Compute the number of bins and the largest useful area.""" + + def __init__(self, instance: Instance) -> None: + """ + Initialize the objective function. + + :param instance: the instance to load the bounds from + """ + super().__init__(instance) + #: the bin width + self.__bin_width: Final[int] = instance.bin_width + #: the bin height + self.__bin_height: Final[int] = instance.bin_height + +
+[docs] + def evaluate(self, x) -> int: + """ + Evaluate the objective function. + + :param x: the solution + :return: the bin size and last-bin-small-area factor + """ + return bin_count_and_lowest_skyline( + x, self.__bin_width, self.__bin_height)
+ + + def __str__(self) -> str: + """ + Get the name of the bins objective function. + + :return: `binCountAndLowestSkyline` + :retval "binCountAndLowestSkyline": always + """ + return "binCountAndLowestSkyline"
+ +
diff --git a/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_small.html b/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_small.html new file mode 100644 index 00000000..90071833 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/objectives/bin_count_and_small.html @@ -0,0 +1,135 @@ +moptipyapps.binpacking2d.objectives.bin_count_and_small — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.objectives.bin_count_and_small

+"""
+An objective function indirectly minimizing the number of bins in packings.
+
+This objective function computes the number of bins used. Let's call it
+`n_bins`. We know the area `bin_area` of a bin as well. We then compute the
+minimum area occupied in any bin, `min_area`. Now we return
+`(bin_area * (n_bins - 1)) + min_area`.
+
+This function always prefers a packing that has fewer bins over a packing
+with more bins duw to the term `bin_area * (n_bins - 1)` and
+`bin_area >= min_area` (let us ignore the case where `bin_area == min_area`,
+which does not make practical sense). Since `min_area < bin_area` in all
+practically relevant cases, the offset `min_area` just distinguishes
+packings that have same number of bins. Amongst such packings, those whose
+least-occupied bin is closer to being empty are preferred (regardless where
+this bin is). The idea is that this will eventually allow us to get rid of
+that least-occupied bin in subsequent optimization steps, i.e., to reduce
+the number of bins.
+
+This is similar to the :mod:`~moptipyapps.binpacking2d.objectives.\
+bin_count_and_small` objective, except that we do not try to expunge the last
+bin, but any bin. It has the same lower and upper bound, though.
+"""
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.objectives.bin_count_and_last_small import (
+    BinCountAndLastSmall,
+)
+from moptipyapps.binpacking2d.packing import (
+    IDX_BIN,
+    IDX_BOTTOM_Y,
+    IDX_LEFT_X,
+    IDX_RIGHT_X,
+    IDX_TOP_Y,
+)
+
+
+
+[docs] +@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) +def bin_count_and_small(y: np.ndarray, bin_area: int, + temp: np.ndarray) -> int: + """ + Compute the number of bins and the smallest occupied area in any bin. + + We compute the total number of bins minus 1 and multiply it with the + total area of items. We then add the area of items in the smallest bin. + + :param y: the packing + :param bin_area: the area of a single bin + :param temp: a temporary array to hold the current area counters + :return: the objective value + + >>> tempa = np.empty(10, int) + >>> bin_count_and_small(np.array([[1, 1, 10, 10, 20, 20], + ... [1, 1, 30, 30, 40, 40], + ... [1, 1, 20, 20, 30, 30]], int), + ... 50*50, tempa) + 300 + >>> bin_count_and_small(np.array([[1, 1, 10, 10, 20, 20], + ... [1, 2, 30, 30, 40, 40], # bin 2! + ... [1, 1, 20, 20, 30, 30]], int), + ... 50*50, tempa) + 2600 + >>> bin_count_and_small(np.array([[1, 2, 10, 10, 20, 20], # bin 2! + ... [1, 2, 30, 30, 40, 40], # bin 2! + ... [1, 1, 20, 20, 30, 30]], int), + ... 50*50, tempa) + 2600 + >>> bin_count_and_small(np.array([[1, 3, 10, 10, 20, 20], # bin 3! + ... [1, 2, 30, 30, 40, 40], # bin 2! + ... [1, 1, 20, 20, 30, 30]], int), + ... 50*50, tempa) + 5100 + >>> bin_count_and_small(np.array([[1, 3, 10, 10, 50, 50], # bin 3! + ... [1, 2, 30, 30, 60, 60], # bin 2! + ... [1, 1, 20, 20, 30, 30]], np.int8), + ... 50*50, tempa) + 5100 + """ + total_bins: int = 0 # the current idea of the number of bins + n_items: Final[int] = len(y) # the number of rows in the matrix + temp.fill(0) # fill temp with zeros + + for i in range(n_items): # iterate over all packed items + bin_idx: int = int(y[i, IDX_BIN]) - 1 # get the bin index of the item + temp[bin_idx] += ((y[i, IDX_RIGHT_X] - y[i, IDX_LEFT_X]) + * (y[i, IDX_TOP_Y] - y[i, IDX_BOTTOM_Y])) + total_bins = max(total_bins, bin_idx) + return (bin_area * total_bins) + temp[0:total_bins + 1].min()
+ + + +
+[docs] +class BinCountAndSmall(BinCountAndLastSmall): + """Compute the number of bins and the area in the smallest one.""" + + def __init__(self, instance: Instance) -> None: + """ + Initialize the objective function. + + :param instance: the instance to load the bounds from + """ + super().__init__(instance) + #: the internal temporary array + self.__temp: Final[np.ndarray] = np.empty(instance.n_items, int) + +
+[docs] + def evaluate(self, x) -> int: + """ + Evaluate the objective function. + + :param x: the solution + :return: the bin size and smallest-bin-area factor + """ + return bin_count_and_small(x, self._bin_size, self.__temp)
+ + + def __str__(self) -> str: + """ + Get the name of the bins objective function. + + :return: `binCountAndSmall` + :retval "binCountAndSmall": always + """ + return "binCountAndSmall"
+ +
diff --git a/_modules/moptipyapps/binpacking2d/packing.html b/_modules/moptipyapps/binpacking2d/packing.html new file mode 100644 index 00000000..53c85fa0 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/packing.html @@ -0,0 +1,167 @@ +moptipyapps.binpacking2d.packing — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.packing

+"""A two-dimensional packing."""
+
+from typing import Final
+
+import numpy as np
+from moptipy.api.component import Component
+from moptipy.api.logging import SECTION_RESULT_Y, SECTION_SETUP
+from moptipy.evaluation.log_parser import LogParser
+from pycommons.types import type_error
+
+from moptipyapps.binpacking2d.instance import Instance
+
+#: the index of the ID in a :class:`Packing` row
+IDX_ID: Final[int] = 0
+#: the index of the bin in a :class:`Packing` row
+IDX_BIN: Final[int] = 1
+#: the index of the left x coordinate in a :class:`Packing` row
+IDX_LEFT_X: Final[int] = 2
+#: the index of the bottom y coordinate in a :class:`Packing` row
+IDX_BOTTOM_Y: Final[int] = 3
+#: the index of the right x coordinate in a :class:`Packing` row
+IDX_RIGHT_X: Final[int] = 4
+#: the index of the top y coordinate in a :class:`Packing` row
+IDX_TOP_Y: Final[int] = 5
+
+
+
+[docs] +class Packing(Component, np.ndarray): + """ + A packing, i.e., a solution to an 2D bin packing instance. + + A packing is a two-dimensional numpy array. In each row, the position of + one item is stored: 1. the item ID (starts at 1), 2. the bin into which + the item is packaged (starts at 1), 3. the left x coordinate, 4. the + bottom y coordinate, 5. the right x coordinate, 6. the top y coordinate. + """ + + #: the 2d bin packing instance + instance: Instance + #: the number of bins + n_bins: int + + def __new__(cls, instance: Instance) -> "Packing": + """ + Create a solution record for the 2D bin packing problem. + + :param cls: the class + :param instance: the solution record + """ + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + obj: Final[Packing] = super().__new__( + cls, (instance.n_items, 6), instance.dtype) + #: the 2d bin packing instance + obj.instance = instance + #: the number of bins + obj.n_bins = -1 + return obj + + def __str__(self): + """ + Convert the packing to a compact string. + + :return: the compact string + """ + return "\n".join(";".join(str(self[i, j]) for j in range(6)) + for i in range(self.shape[0])) + +
+[docs] + @staticmethod + def from_log(file: str, instance: Instance | None = None) -> "Packing": + """ + Load a packing from a log file. + + :param file: the log file path + :param instance: the optional Packing instance: if `None` is provided, + we try to load it from the resources + :returns: the Packing + """ + parser: Final[_PackingParser] = _PackingParser(instance) + parser.parse_file(file) + # noinspection PyProtectedMember + res = parser._result + if not isinstance(res, Packing): + raise type_error(res, f"packing from {file!r}", Packing) + return res
+
+ + + +class _PackingParser(LogParser): + """The log parser for loading packings.""" + + def __init__(self, instance: Instance | None = None): + """ + Create the packing parser. + + :param instance: the optional packing instance: if `None` is provided, + we try to load it from the resources + """ + super().__init__() + if (instance is not None) and (not isinstance(instance, Instance)): + raise type_error(instance, "instance", Instance) + #: the internal instance + self.__instance: Instance | None = instance + #: the internal section mode: 0=none, 1=setup, 2=y + self.__sec_mode: int = 0 + #: the packing string + self.__packing_str: str | None = None + #: the result packing + self._result: Packing | None = None + + def start_section(self, title: str) -> bool: + """Start a section.""" + super().start_section(title) + self.__sec_mode = 0 + if title == SECTION_SETUP: + if self.__instance is None: + self.__sec_mode = 1 + return True + return False + if title == SECTION_RESULT_Y: + self.__sec_mode = 2 + return True + return False + + def lines(self, lines: list[str]) -> bool: + """Parse the lines.""" + if self.__sec_mode == 1: + if self.__instance is not None: + raise ValueError( + f"instance is already set to {self.__instance}.") + key_1: Final[str] = "y.inst.name: " + for line in lines: + if line.startswith(key_1): + self.__instance = Instance.from_resource( + line[len(key_1):].strip()) + if self.__instance is None: + raise ValueError(f"Did not find instance key {key_1!r} " + f"in section {SECTION_SETUP}!") + elif self.__sec_mode == 2: + self.__packing_str = " ".join(lines).strip() + else: + raise ValueError("Should not be in section?") + return (self.__instance is None) or (self.__packing_str is None) + + def end_file(self) -> bool: + """End the file.""" + if self.__packing_str is None: + raise ValueError(f"Section {SECTION_RESULT_Y} missing!") + if self.__instance is None: + raise ValueError(f"Section {SECTION_SETUP} missing or empty!") + if self._result is not None: + raise ValueError("Applied parser to more than one log file?") + # pylint: disable=C0415,R0401 + from moptipyapps.binpacking2d.packing_space import ( + PackingSpace, # pylint: disable=C0415,R0401 + ) + + self._result = PackingSpace(self.__instance)\ + .from_str(self.__packing_str) + self.__packing_str = None + return False +
diff --git a/_modules/moptipyapps/binpacking2d/packing_result.html b/_modules/moptipyapps/binpacking2d/packing_result.html new file mode 100644 index 00000000..33aba4f9 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/packing_result.html @@ -0,0 +1,760 @@ +moptipyapps.binpacking2d.packing_result — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.packing_result

+"""
+An extended end result record to represent packings.
+
+This class extends the information provided by
+:mod:`~moptipy.evaluation.end_results`. It allows us to compare the results
+of experiments over different objective functions. It also represents the
+bounds for the number of bins and for the objective functions. It also
+includes the problem-specific information of two-dimensional bin packing
+instances.
+"""
+import argparse
+from dataclasses import dataclass
+from math import isfinite
+from typing import Any, Callable, Final, Iterable, Mapping, cast
+
+from moptipy.api.objective import Objective
+from moptipy.evaluation.base import (
+    EvaluationDataElement,
+)
+from moptipy.evaluation.end_results import CsvReader as ErCsvReader
+from moptipy.evaluation.end_results import CsvWriter as ErCsvWriter
+from moptipy.evaluation.end_results import EndResult
+from moptipy.evaluation.end_results import from_logs as er_from_logs
+from moptipy.evaluation.log_parser import LogParser
+from pycommons.ds.immutable_map import immutable_mapping
+from pycommons.ds.sequences import reiterable
+from pycommons.io.console import logger
+from pycommons.io.csv import (
+    SCOPE_SEPARATOR,
+    csv_column,
+    csv_read,
+    csv_scope,
+    csv_select_scope,
+    csv_write,
+)
+from pycommons.io.path import Path, file_path, line_writer
+from pycommons.strings.string_conv import (
+    num_to_str,
+    str_to_num,
+)
+from pycommons.types import check_int_range, type_error
+
+from moptipyapps.binpacking2d.instance import (
+    Instance,
+    _lower_bound_damv,
+)
+from moptipyapps.binpacking2d.objectives.bin_count import (
+    BIN_COUNT_NAME,
+    BinCount,
+)
+from moptipyapps.binpacking2d.objectives.bin_count_and_empty import (
+    BinCountAndEmpty,
+)
+from moptipyapps.binpacking2d.objectives.bin_count_and_last_empty import (
+    BinCountAndLastEmpty,
+)
+from moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline import (
+    BinCountAndLastSkyline,
+)
+from moptipyapps.binpacking2d.objectives.bin_count_and_last_small import (
+    BinCountAndLastSmall,
+)
+from moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline import (
+    BinCountAndLowestSkyline,
+)
+from moptipyapps.binpacking2d.objectives.bin_count_and_small import (
+    BinCountAndSmall,
+)
+from moptipyapps.binpacking2d.packing import Packing
+from moptipyapps.shared import (
+    moptipyapps_argparser,
+    motipyapps_footer_bottom_comments,
+)
+
+#: the number of items
+KEY_N_ITEMS: Final[str] = "nItems"
+#: the number of different items
+KEY_N_DIFFERENT_ITEMS: Final[str] = "nDifferentItems"
+#: the bin width
+KEY_BIN_WIDTH: Final[str] = "binWidth"
+#: the bin height
+KEY_BIN_HEIGHT: Final[str] = "binHeight"
+
+
+#: the default objective functions
+DEFAULT_OBJECTIVES: Final[tuple[Callable[[Instance], Objective], ...]] = (
+    BinCount, BinCountAndLastEmpty, BinCountAndLastSmall,
+    BinCountAndLastSkyline, BinCountAndEmpty, BinCountAndSmall,
+    BinCountAndLowestSkyline)
+
+
+def __lb_geometric(inst: Instance) -> int:
+    """
+    Compute the geometric lower bound.
+
+    :param inst: the instance
+    :return: the lower bound
+    """
+    area: Final[int] = sum(int(row[0]) * int(row[1]) * int(row[2])
+                           for row in inst)
+    bin_size: Final[int] = inst.bin_width * inst.bin_height
+    res: int = area // bin_size
+    return (res + 1) if ((res * bin_size) != area) else res
+
+
+#: the lower bound of an objective
+_OBJECTIVE_LOWER: Final[str] = "lowerBound"
+#: the upper bound of an objective
+_OBJECTIVE_UPPER: Final[str] = "upperBound"
+#: the start string for bin bounds
+LOWER_BOUNDS_BIN_COUNT: Final[str] = csv_scope("bins", _OBJECTIVE_LOWER)
+#: the default bounds
+_DEFAULT_BOUNDS: Final[Mapping[str, Callable[[Instance], int]]] = \
+    immutable_mapping({
+        LOWER_BOUNDS_BIN_COUNT: lambda i: i.lower_bound_bins,
+        csv_scope(LOWER_BOUNDS_BIN_COUNT, "geometric"): __lb_geometric,
+        csv_scope(LOWER_BOUNDS_BIN_COUNT, "damv"): lambda i: _lower_bound_damv(
+            i.bin_width, i.bin_height, i),
+    })
+
+
+
+[docs] +@dataclass(frozen=True, init=False, order=False, eq=False) +class PackingResult(EvaluationDataElement): + """ + An end result record of one run of one packing algorithm on one problem. + + This record provides the information of the outcome of one application of + one algorithm to one problem instance in an immutable way. + """ + + #: the original end result record + end_result: EndResult + #: the number of items in the instance + n_items: int + #: the number of different items in the instance + n_different_items: int + #: the bin width + bin_width: int + #: the bin height + bin_height: int + #: the objective values evaluated after the optimization + objectives: Mapping[str, int | float] + #: the bounds for the objective values (append ".lowerBound" and + #: ".upperBound" to all objective function names) + objective_bounds: Mapping[str, int | float] + #: the bounds for the minimum number of bins of the instance + bin_bounds: Mapping[str, int] + + def __init__(self, + end_result: EndResult, + n_items: int, + n_different_items: int, + bin_width: int, + bin_height: int, + objectives: Mapping[str, int | float], + objective_bounds: Mapping[str, int | float], + bin_bounds: Mapping[str, int]): + """ + Create a consistent instance of :class:`PackingResult`. + + :param end_result: the end result + :param n_items: the number of items + :param n_different_items: the number of different items + :param bin_width: the bin width + :param bin_height: the bin height + :param objectives: the objective values computed after the + optimization + :param bin_bounds: the different bounds for the number of bins + :param objective_bounds: the bounds for the objective functions + :raises TypeError: if any parameter has a wrong type + :raises ValueError: if the parameter values are inconsistent + """ + super().__init__() + if not isinstance(end_result, EndResult): + raise type_error(end_result, "end_result", EndResult) + if end_result.best_f != objectives[end_result.objective]: + raise ValueError( + f"end_result.best_f={end_result.best_f}, but objectives[" + f"{end_result.objective!r}]=" + f"{objectives[end_result.objective]}.") + if not isinstance(objectives, Mapping): + raise type_error(objectives, "objectives", Mapping) + if not isinstance(objective_bounds, Mapping): + raise type_error(objective_bounds, "objective_bounds", Mapping) + if not isinstance(bin_bounds, Mapping): + raise type_error(bin_bounds, "bin_bounds", Mapping) + if len(objective_bounds) != (2 * len(objectives)): + raise ValueError(f"it is required that there is a lower and an " + f"upper bound for each of the {len(objectives)} " + f"functions, but we got {len(objective_bounds)} " + f"bounds, objectives={objectives}, " + f"objective_bounds={objective_bounds}.") + + for name, value in objectives.items(): + if not isinstance(name, str): + raise type_error( + name, f"name of evaluation[{name!r}]={value!r}", str) + if not isinstance(value, int | float): + raise type_error( + value, f"value of evaluation[{name!r}]={value!r}", + (int, float)) + if not isfinite(value): + raise ValueError( + f"non-finite value of evaluation[{name!r}]={value!r}") + lll: str = csv_scope(name, _OBJECTIVE_LOWER) + lower = objective_bounds[lll] + if not isfinite(lower): + raise ValueError(f"{lll}=={lower}.") + uuu = csv_scope(name, _OBJECTIVE_UPPER) + upper = objective_bounds[uuu] + if not (lower <= value <= upper): + raise ValueError( + f"it is required that {lll}<=f<={uuu}, but got " + f"{lower}, {value}, and {upper}.") + + bins: Final[int | None] = cast(int, objectives[BIN_COUNT_NAME]) \ + if BIN_COUNT_NAME in objectives else None + for name, value in bin_bounds.items(): + if not isinstance(name, str): + raise type_error( + name, f"name of bounds[{name!r}]={value!r}", str) + check_int_range(value, f"bounds[{name!r}]", 1, 1_000_000_000) + if (bins is not None) and (bins < value): + raise ValueError( + f"number of bins={bins} is inconsistent with " + f"bound {name!r}={value}.") + + object.__setattr__(self, "end_result", end_result) + object.__setattr__(self, "objectives", immutable_mapping(objectives)) + object.__setattr__(self, "objective_bounds", + immutable_mapping(objective_bounds)) + object.__setattr__(self, "bin_bounds", immutable_mapping(bin_bounds)) + object.__setattr__(self, "n_different_items", check_int_range( + n_different_items, "n_different_items", 1, 1_000_000_000_000)) + object.__setattr__(self, "n_items", check_int_range( + n_items, "n_items", n_different_items, 1_000_000_000_000)) + object.__setattr__(self, "bin_width", check_int_range( + bin_width, "bin_width", 1, 1_000_000_000_000)) + object.__setattr__(self, "bin_height", check_int_range( + bin_height, "bin_height", 1, 1_000_000_000_000)) + + def _tuple(self) -> tuple[Any, ...]: + """ + Create a tuple with all the data of this data class for comparison. + + :returns: a tuple with all the data of this class, where `None` values + are masked out + """ + # noinspection PyProtectedMember + return self.end_result._tuple()
+ + + +
+[docs] +def from_packing_and_end_result( # pylint: disable=W0102 + end_result: EndResult, packing: Packing, + objectives: Iterable[Callable[[Instance], Objective]] = + DEFAULT_OBJECTIVES, + bin_bounds: Mapping[str, Callable[[Instance], int]] = + _DEFAULT_BOUNDS, + cache: Mapping[str, tuple[Mapping[str, int], tuple[ + Objective, ...], Mapping[str, int | float]]] | None = + None) -> PackingResult: + """ + Create a `PackingResult` from an `EndResult` and a `Packing`. + + :param end_result: the end results record + :param packing: the packing + :param bin_bounds: the bounds computing functions + :param objectives: the objective function factories + :param cache: a cache that can store stuff if this function is to be + called repeatedly + :return: the packing result + """ + if not isinstance(end_result, EndResult): + raise type_error(end_result, "end_result", EndResult) + if not isinstance(packing, Packing): + raise type_error(packing, "packing", Packing) + if not isinstance(objectives, Iterable): + raise type_error(objectives, "objectives", Iterable) + if not isinstance(bin_bounds, Mapping): + raise type_error(bin_bounds, "bin_bounds", Mapping) + if (cache is not None) and (not isinstance(cache, dict)): + raise type_error(cache, "cache", (None, dict)) + + instance: Final[Instance] = packing.instance + if instance.name != end_result.instance: + raise ValueError( + f"packing.instance.name={instance.name!r}, but " + f"end_result.instance={end_result.instance!r}.") + + row: tuple[Mapping[str, int], tuple[Objective, ...], + Mapping[str, int | float]] | None = None \ + if (cache is None) else cache.get(instance.name, None) + if row is None: + objfs = tuple(sorted((obj(instance) for obj in objectives), + key=str)) + obounds = {} + for objf in objfs: + obounds[csv_scope(str(objf), _OBJECTIVE_LOWER)] = \ + objf.lower_bound() + obounds[csv_scope(str(objf), _OBJECTIVE_UPPER)] = \ + objf.upper_bound() + row = ({key: bin_bounds[key](instance) + for key in sorted(bin_bounds.keys())}, objfs, + immutable_mapping(obounds)) + if cache is not None: + cache[instance.name] = row + + objective_values: dict[str, int | float] = {} + bin_count: int = -1 + bin_count_obj: str = "" + for objf in row[1]: + z: int | float = objf.evaluate(packing) + objfn: str = str(objf) + objective_values[objfn] = z + if not isinstance(z, int): + continue + if not isinstance(objf, BinCount): + continue + bc: int = objf.to_bin_count(z) + if bin_count == -1: + bin_count = bc + bin_count_obj = objfn + elif bin_count != bc: + raise ValueError( + f"found bin count disagreement: {bin_count} of " + f"{bin_count_obj!r} != {bc} of {objf!r}") + + return PackingResult( + end_result=end_result, + n_items=instance.n_items, + n_different_items=instance.n_different_items, + bin_width=instance.bin_width, bin_height=instance.bin_height, + objectives=objective_values, + objective_bounds=row[2], + bin_bounds=row[0])
+ + + +
+[docs] +def from_single_log( # pylint: disable=W0102 + file: str, + objectives: Iterable[Callable[[Instance], Objective]] = + DEFAULT_OBJECTIVES, + bin_bounds: Mapping[str, Callable[[Instance], int]] = + _DEFAULT_BOUNDS, + cache: Mapping[str, tuple[Mapping[str, int], tuple[ + Objective, ...], Mapping[str, int | float]]] | None = + None) -> PackingResult: + """ + Create a `PackingResult` from a file. + + :param file: the file path + :param objectives: the objective function factories + :param bin_bounds: the bounds computing functions + :param cache: a cache that can store stuff if this function is to be + called repeatedly + :return: the packing result + """ + the_file_path = file_path(file) + end_results: Final[list[EndResult]] = [] + er_from_logs(the_file_path, end_results.append) + if len(end_results) != 1: + raise ValueError( + f"needs one end result record in file {the_file_path!r}, " + f"but got {end_results}.") + + packing = Packing.from_log(the_file_path) + if not isinstance(packing, Packing): + raise type_error(packing, f"packing from {file!r}", Packing) + return from_packing_and_end_result( + end_result=end_results[0], packing=packing, + objectives=objectives, bin_bounds=bin_bounds, cache=cache)
+ + + +
+[docs] +def from_logs( # pylint: disable=W0102 + directory: str, + consumer: Callable[[PackingResult], None], + objectives: Iterable[Callable[[Instance], Objective]] = + DEFAULT_OBJECTIVES, + bin_bounds: Mapping[str, Callable[[Instance], int]] + = _DEFAULT_BOUNDS) -> None: + """ + Parse a directory recursively to get all packing results. + + :param directory: the directory to parse + :param consumer: the consumer for receiving the results + :param objectives: the objective function factories + :param bin_bounds: the bin bounds calculators + """ + __LogParser(consumer, objectives, bin_bounds).parse_dir(directory)
+ + + +
+[docs] +def to_csv(results: Iterable[PackingResult], file: str) -> Path: + """ + Write a sequence of packing results to a file in CSV format. + + :param results: the end results + :param file: the path + :return: the path of the file that was written + """ + path: Final[Path] = Path(file) + logger(f"Writing packing results to CSV file {path!r}.") + path.ensure_parent_dir_exists() + with path.open_for_write() as wt: + consumer: Final[Callable[[str], None]] = line_writer(wt) + for p in csv_write( + data=sorted(results), + setup=CsvWriter().setup, + column_titles=CsvWriter.get_column_titles, + get_row=CsvWriter.get_row, + header_comments=CsvWriter.get_header_comments, + footer_comments=CsvWriter.get_footer_comments, + footer_bottom_comments=CsvWriter.get_footer_bottom_comments): + consumer(p) + logger(f"Done writing packing results to CSV file {path!r}.") + return path
+ + + +
+[docs] +def from_csv(file: str) -> Iterable[PackingResult]: + """ + Load the packing results from a CSV file. + + :param file: the file to read from + :returns: the iterable with the packing result + """ + path: Final[Path] = file_path(file) + logger(f"Now reading CSV file {path!r}.") + with path.open_for_read() as rd: + yield from csv_read( + rows=rd, setup=CsvReader, parse_row=CsvReader.parse_row) + logger(f"Done reading CSV file {path!r}.")
+ + + +
+[docs] +class CsvWriter: + """A class for CSV writing of :class:`PackingResult`.""" + + def __init__(self, scope: str | None = None) -> None: + """ + Initialize the csv writer. + + :param scope: the prefix to be pre-pended to all columns + """ + #: an optional scope + self.scope: Final[str | None] = ( + str.strip(scope)) if scope is not None else None + + #: has this writer been set up? + self.__setup: bool = False + #: the end result writer + self.__er: Final[ErCsvWriter] = ErCsvWriter(scope) + #: the bin bounds + self.__bin_bounds: list[str] | None = None + #: the objectives + self.__objectives: list[str] | None = None + +
+[docs] + def setup(self, data: Iterable[PackingResult]) -> "CsvWriter": + """ + Set up this csv writer based on existing data. + + :param data: the data to setup with + :returns: this writer + """ + if self.__setup: + raise ValueError("CSV writer has already been set up.") + self.__setup = True + + data = reiterable(data) + self.__er.setup(pr.end_result for pr in data) + + bin_bounds_set: Final[set[str]] = set() + objectives_set: Final[set[str]] = set() + for pr in data: + bin_bounds_set.update(pr.bin_bounds.keys()) + objectives_set.update(pr.objectives.keys()) + if set.__len__(bin_bounds_set) > 0: + self.__bin_bounds = sorted(bin_bounds_set) + if set.__len__(objectives_set) > 0: + self.__objectives = sorted(objectives_set) + + return self
+ + +
+[docs] + def get_column_titles(self) -> Iterable[str]: + """ + Get the column titles. + + :returns: the column titles + """ + p: Final[str] = self.scope + yield from self.__er.get_column_titles() + + yield csv_scope(p, KEY_BIN_HEIGHT) + yield csv_scope(p, KEY_BIN_WIDTH) + yield csv_scope(p, KEY_N_ITEMS) + yield csv_scope(p, KEY_N_DIFFERENT_ITEMS) + if self.__bin_bounds: + for b in self.__bin_bounds: + yield csv_scope(p, b) + if self.__objectives: + for o in self.__objectives: + oo: str = csv_scope(p, o) + yield csv_scope(oo, _OBJECTIVE_LOWER) + yield oo + yield csv_scope(oo, _OBJECTIVE_UPPER)
+ + +
+[docs] + def get_row(self, data: PackingResult) -> Iterable[str]: + """ + Render a single packing result record to a CSV row. + + :param data: the end result record + :returns: the iterable with the row data + """ + yield from self.__er.get_row(data.end_result) + yield repr(data.bin_height) + yield repr(data.bin_width) + yield repr(data.n_items) + yield repr(data.n_different_items) + if self.__bin_bounds: + for bb in self.__bin_bounds: + yield (repr(data.bin_bounds[bb]) + if bb in data.bin_bounds else "") + if self.__objectives: + for ob in self.__objectives: + ox = csv_scope(ob, _OBJECTIVE_LOWER) + yield (num_to_str(data.objective_bounds[ox]) + if ox in data.objective_bounds else "") + yield (num_to_str(data.objectives[ob]) + if ob in data.objectives else "") + ox = csv_scope(ob, _OBJECTIVE_UPPER) + yield (num_to_str(data.objective_bounds[ox]) + if ox in data.objective_bounds else "")
+ + +
+[docs] + def get_header_comments(self) -> Iterable[str]: + """ + Get any possible header comments. + + :returns: the header comments + """ + return ("End Results of Bin Packing Experiments", + "See the description at the bottom of the file.")
+ + + + + + +
+ + + +
+[docs] +class CsvReader: + """A class for CSV reading of :class:`PackingResult` instances.""" + + def __init__(self, columns: dict[str, int]) -> None: + """ + Create a CSV parser for :class:`EndResult`. + + :param columns: the columns + """ + super().__init__() + #: the end result csv reader + self.__er: Final[ErCsvReader] = ErCsvReader(columns) + #: the index of the n-items column + self.__idx_n_items: Final[int] = csv_column(columns, KEY_N_ITEMS) + #: the index of the n different items column + self.__idx_n_different: Final[int] = csv_column( + columns, KEY_N_DIFFERENT_ITEMS) + #: the index of the bin width column + self.__idx_bin_width: Final[int] = csv_column(columns, KEY_BIN_WIDTH) + #: the index of the bin height column + self.__idx_bin_height: Final[int] = csv_column( + columns, KEY_BIN_HEIGHT) + #: the indices for the objective bounds + self.__bin_bounds: Final[tuple[tuple[str, int], ...]] = \ + csv_select_scope( + lambda x: tuple(sorted(((k, v) for k, v in x.items()))), + columns, LOWER_BOUNDS_BIN_COUNT) + #: the objective bounds + self.__objective_bounds: Final[tuple[tuple[str, int], ...]] = \ + csv_select_scope( + lambda x: tuple(sorted(((k, v) for k, v in x.items()))), + columns, None, + skip_orig_key=lambda s: not str.endswith( + s, (_OBJECTIVE_LOWER, _OBJECTIVE_UPPER))) + n_bounds: Final[int] = tuple.__len__(self.__objective_bounds) + if n_bounds <= 0: + raise ValueError("No objective function bounds found?") + if (n_bounds & 1) != 0: + raise ValueError(f"Number of bounds {n_bounds} should be even.") + #: the parsers for the objective values + self.__objectives: Final[tuple[tuple[str, int], ...]] = \ + tuple((ss, csv_column(columns, ss)) + for ss in sorted({s[0] for s in (str.split( + kk[0], SCOPE_SEPARATOR) for kk in + self.__objective_bounds) if (list.__len__(s) > 1) + and (str.__len__(s[0]) > 0)})) + n_objectives: Final[int] = tuple.__len__(self.__objectives) + if n_objectives <= 0: + raise ValueError("No objectives found?") + if (2 * n_objectives) != n_bounds: + raise ValueError( + f"Number {n_objectives} of objectives " + f"inconsistent with number {n_bounds} of bounds.") + +
+[docs] + def parse_row(self, data: list[str]) -> PackingResult: + """ + Parse a row of data. + + :param data: the data row + :return: the end result statistics + """ + return PackingResult( + self.__er.parse_row(data), + int(data[self.__idx_n_items]), + int(data[self.__idx_n_different]), + int(data[self.__idx_bin_width]), + int(data[self.__idx_bin_height]), + {n: str_to_num(data[i]) for n, i in self.__objectives + if str.__len__(data[i]) > 0}, + {n: str_to_num(data[i]) for n, i in self.__objective_bounds + if str.__len__(data[i]) > 0}, + {n: int(data[i]) for n, i in self.__bin_bounds + if str.__len__(data[i]) > 0})
+
+ + + +class __LogParser(LogParser): + """The internal log parser class.""" + + def __init__(self, consumer: Callable[[PackingResult], None], + objectives: Iterable[Callable[[Instance], Objective]], + bin_bounds: Mapping[str, Callable[[Instance], int]]) -> None: + """ + Parse a directory recursively to get all packing results. + + :param consumer: the consumer for receiving the results + :param objectives: the objective function factories + :param bin_bounds: the bin bounds calculators + """ + super().__init__() + if not callable(consumer): + raise type_error(consumer, "consumer", call=True) + if not isinstance(objectives, Iterable): + raise type_error(objectives, "objectives", Iterable) + if not isinstance(bin_bounds, Mapping): + raise type_error(bin_bounds, "bin_bounds", Mapping) + #: the internal consumer + self.__consumer: Final[Callable[[PackingResult], None]] = consumer + #: the objectives holder + self.__objectives: Final[ + Iterable[Callable[[Instance], Objective]]] = objectives + #: the bin bounds + self.__bin_bounds: Final[ + Mapping[str, Callable[[Instance], int]]] = bin_bounds + #: the internal cache + self.__cache: Final[Mapping[ + str, tuple[Mapping[str, int], tuple[ + Objective, ...], Mapping[str, int | float]]]] = {} + + def parse_file(self, path: str) -> bool: + """ + Parse a log file. + + :param path: the path to the log file + :return: `True` + """ + self.__consumer(from_single_log(path, self.__objectives, + self.__bin_bounds, self.__cache)) + return True + + +# Evaluate an experiment from the command line + +# Run log files to end results if executed as script +if __name__ == "__main__": + parser: Final[argparse.ArgumentParser] = moptipyapps_argparser( + __file__, + "Convert log files for the bin packing experiment to a CSV file.", + "Re-evaluate all results based on different objective functions.") + parser.add_argument( + "source", nargs="?", default="./results", + help="the location of the experimental results, i.e., the root folder " + "under which to search for log files", type=Path) + parser.add_argument( + "dest", help="the path to the end results CSV file to be created", + type=Path, nargs="?", default="./evaluation/end_results.txt") + args: Final[argparse.Namespace] = parser.parse_args() + + packing_results: Final[list[PackingResult]] = [] + from_logs(args.source, packing_results.append) + to_csv(packing_results, args.dest) +
diff --git a/_modules/moptipyapps/binpacking2d/packing_space.html b/_modules/moptipyapps/binpacking2d/packing_space.html new file mode 100644 index 00000000..4eca1ebd --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/packing_space.html @@ -0,0 +1,373 @@ +moptipyapps.binpacking2d.packing_space — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.packing_space

+"""
+Here we provide a :class:`~moptipy.api.space.Space` of bin packings.
+
+The bin packing object is defined in module
+:mod:`~moptipyapps.binpacking2d.packing`. Basically, it is a
+two-dimensional numpy array holding, for each item, its ID, its bin ID, as
+well as its location defined by four coordinates.
+
+1. Dequan Liu and Hongfei Teng. An Improved BL-Algorithm for Genetic Algorithm
+   of the Orthogonal Packing of Rectangles. European Journal of Operational
+   Research. 112(2):413-420. January (1999).
+   https://doi.org/10.1016/S0377-2217(97)00437-2.
+   http://www.paper.edu.cn/scholar/showpdf/MUT2AN0IOTD0Mxxh.
+"""
+from collections import Counter
+from math import factorial
+from typing import Final
+
+import numpy as np
+from moptipy.api.space import Space
+from moptipy.utils.logger import CSV_SEPARATOR, KeyValueLogSection
+from pycommons.types import check_int_range, type_error
+
+from moptipyapps.binpacking2d.instance import (
+    IDX_HEIGHT,
+    IDX_REPETITION,
+    IDX_WIDTH,
+    Instance,
+)
+from moptipyapps.binpacking2d.packing import (
+    IDX_BIN,
+    IDX_BOTTOM_Y,
+    IDX_ID,
+    IDX_LEFT_X,
+    IDX_RIGHT_X,
+    IDX_TOP_Y,
+    Packing,
+)
+from moptipyapps.shared import SCOPE_INSTANCE
+
+
+
+[docs] +class PackingSpace(Space): + """An implementation of the `Space` API of for 2D bin packings charts.""" + + def __init__(self, instance: Instance) -> None: + """ + Create a 2D packing space. + + :param instance: the 2d bin packing instance + + >>> inst = Instance.from_resource("a01") + >>> space = PackingSpace(inst) + >>> space.instance is inst + True + """ + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + #: The instance to which the packings apply. + self.instance: Final[Instance] = instance + +
+[docs] + def copy(self, dest: Packing, source: Packing) -> None: + """ + Copy one packing to another one. + + :param dest: the destination packing + :param source: the source packing + """ + np.copyto(dest, source) + dest.n_bins = source.n_bins
+ + +
+[docs] + def create(self) -> Packing: + """ + Create a packing object without assigning items to locations. + + :return: the (empty, uninitialized) packing object + + >>> inst = Instance.from_resource("a01") + >>> space = PackingSpace(inst) + >>> x = space.create() + >>> x.shape + (24, 6) + >>> x.instance is inst + True + >>> type(x) + <class 'moptipyapps.binpacking2d.packing.Packing'> + """ + return Packing(self.instance)
+ + +
+[docs] + def to_str(self, x: Packing) -> str: + """ + Convert a packing to a string. + + :param x: the packing + :return: a string corresponding to the flattened packing array + + >>> inst = Instance.from_resource("a01") + >>> space = PackingSpace(inst) + >>> y = space.create() + >>> xx = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ... 1, 2, 2, 2, 2, 2, 2]) + >>> import moptipyapps.binpacking2d.encodings.ibl_encoding_1 as e + >>> e._decode(xx, y, inst, inst.bin_width, inst.bin_height) + 5 + >>> space.to_str(y) + '1;1;0;0;463;386;1;1;463;0;926;386;1;1;926;0;1389;386;\ +1;1;1389;0;1852;386;1;1;1852;0;2315;386;1;1;0;386;463;772;\ +1;1;463;386;926;772;1;1;926;386;1389;772;1;1;1389;386;1852;772;\ +1;1;1852;386;2315;772;1;1;0;772;463;1158;1;1;463;772;926;1158;\ +1;1;926;772;1389;1158;1;1;1389;772;1852;1158;1;1;1852;772;2315;1158;\ +1;2;0;0;463;386;1;2;463;0;926;386;1;2;926;0;1389;386;2;2;0;386;1680;806;\ +2;3;0;0;1680;420;2;3;0;420;1680;840;2;4;0;0;1680;420;2;4;0;420;1680;840;\ +2;5;0;0;1680;420' + """ + return CSV_SEPARATOR.join([str(xx) for xx in np.nditer(x)])
+ + +
+[docs] + def is_equal(self, x1: Packing, x2: Packing) -> bool: + """ + Check if two bin packings have the same contents. + + :param x1: the first packing + :param x2: the second packing + :return: `True` if both packings are for the same instance and have the + same structure + + >>> inst = Instance.from_resource("a01") + >>> space = PackingSpace(inst) + >>> y1 = space.create() + >>> y1.fill(0) + >>> y2 = space.create() + >>> y2.fill(0) + >>> space.is_equal(y1, y2) + True + >>> y1 is y2 + False + >>> y1[0, 0] = 1 + >>> space.is_equal(y1, y2) + False + """ + return (x1.instance is x2.instance) and np.array_equal(x1, x2)
+ + +
+[docs] + def from_str(self, text: str) -> Packing: + """ + Convert a string to a packing. + + :param text: the string + :return: the packing + + >>> inst = Instance.from_resource("a01") + >>> space = PackingSpace(inst) + >>> y1 = space.create() + >>> xx = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ... 1, 2, 2, 2, 2, 2, 2]) + >>> import moptipyapps.binpacking2d.encodings.ibl_encoding_1 as e + >>> y1.n_bins = e._decode( + ... xx, y1, inst, inst.bin_width, inst.bin_height) + >>> y2 = space.from_str(space.to_str(y1)) + >>> space.is_equal(y1, y2) + True + >>> y1 is y2 + False + """ + if not isinstance(text, str): + raise type_error(text, "packing text", str) + x: Final[Packing] = self.create() + np.copyto(x, np.fromstring(text, dtype=x.dtype, sep=CSV_SEPARATOR) + .reshape(x.shape)) + x.n_bins = int(x[:, IDX_BIN].max()) + self.validate(x) + return x
+ + +
+[docs] + def validate(self, x: Packing) -> None: + """ + Check if a packing is valid and feasible. + + This method performs a comprehensive feasibility and sanity check of + a packing. It ensures that the packing could be implemented in the + real world exactly as it is given here and that all data are valid and + that it matches to the bin packing instance. This includes: + + - checking that all data types, numpy `dtypes`, and matrix shapes are + correct + - checking that the packing belongs to the same instance as this space + - checking that no objects in the same bin overlap + - checking that all objects occur exactly as often as prescribed by + the instance + - checking that no object extends outside of its bin + - checking that all objects have the same width/height as prescribed + in the instance (with possible rotations) + - check that bin ids are assigned starting at `1` and incrementing in + steps of `1` + + :param x: the packing + :raises TypeError: if any component of the packing is of the wrong + type + :raises ValueError: if the packing is not feasible + """ + if not isinstance(x, Packing): + raise type_error(x, "x", Packing) + inst: Final[Instance] = self.instance + if inst is not x.instance: + raise ValueError( + f"x.instance must be {inst} but is {x.instance}.") + if inst.dtype is not x.dtype: + raise ValueError( + f"inst.dtype = {inst.dtype} but x.dtype={x.dtype}") + needed_shape: Final[tuple[int, int]] = (inst.n_items, 6) + if x.shape != needed_shape: + raise ValueError(f"x.shape={x.shape}, but must be {needed_shape}.") + + bin_width: Final[int] = check_int_range( + inst.bin_width, "bin_width", 1, 1_000_000_000) + bin_height: Final[int] = check_int_range( + inst.bin_height, "bin_height", 1, 1_000_000_000) + + bins: Final[set[int]] = set() + items: Final[Counter[int]] = Counter() + + for i in range(inst.n_items): + item_id: int = int(x[i, IDX_ID]) + if (item_id <= 0) or (item_id > inst.n_different_items): + raise ValueError( + f"Encountered invalid id={item_id} for object at index " + f"{i}, must be in 1..{inst.n_different_items}.") + + bin_id: int = int(x[i, IDX_BIN]) + if (bin_id <= 0) or (bin_id > inst.n_items): + raise ValueError( + f"Encountered invalid bin-id={bin_id} for object at index" + f" {i}, must be in 1..{inst.n_items}.") + bins.add(bin_id) + + items[item_id] += 1 + x_left: int = int(x[i, IDX_LEFT_X]) + y_bottom: int = int(x[i, IDX_BOTTOM_Y]) + x_right: int = int(x[i, IDX_RIGHT_X]) + y_top: int = int(x[i, IDX_TOP_Y]) + + if (x_left >= x_right) or (y_bottom >= y_top): + raise ValueError( + f"Invalid item coordinates ({x_left}, {y_bottom}, " + f"{x_right}, {y_top}) for item of id {item_id} at index" + f" {i}.") + if (x_left < 0) or (y_bottom < 0) or (x_right > bin_width) \ + or (y_top > bin_height): + raise ValueError( + f"Item coordinates ({x_left}, {y_bottom}, {x_right}, " + f"{y_top}) for item of id {item_id} at index {i} extend " + f"outside of the bin ({bin_width}, {bin_height}).") + + real_width: int = x_right - x_left + real_height: int = y_top - y_bottom + width: int = int(inst[item_id - 1, IDX_WIDTH]) + height: int = int(inst[item_id - 1, IDX_HEIGHT]) + + if ((real_width != width) or (real_height != height)) \ + and ((real_width != height) and (real_height != width)): + raise ValueError( + f"Item coordinates ({x_left}, {y_bottom}, {x_right}, " + f"{y_top}) mean width={real_width} and height=" + f"{real_height} for item of id {item_id} at index {i}, " + f"which should have width={width} and height={height}.") + + for j in range(inst.n_items): + if (int(x[j, IDX_BIN]) != bin_id) or (i == j): + continue + x_left_2: int = int(x[j, IDX_LEFT_X]) + y_bottom_2: int = int(x[j, IDX_BOTTOM_Y]) + x_right_2: int = int(x[j, IDX_RIGHT_X]) + y_top_2: int = int(x[j, IDX_TOP_Y]) + if (x_left_2 < x_right) and (x_right_2 > x_left) \ + and (y_bottom_2 < y_top) and (y_top_2 > y_bottom): + raise ValueError( + f"Item {x[j, IDX_ID]} in bin {bin_id} and at index " + f"{j} is ({x_left_2}, {y_bottom_2}, {x_right_2}, " + f"{y_top_2}) and thus intersects with item {item_id} " + f"at index {i} which is in the same bin at ({x_left}," + f" {y_bottom}, {x_right}, {y_top}).") + + for item_id, count in items.items(): + should: int = int(inst[item_id - 1, IDX_REPETITION]) + if should != count: + raise ValueError( + f"Item {item_id} should occur {should} times, but occurs" + f" {count} times instead.") + + max_bin: Final[int] = max(bins) + min_bin: Final[int] = min(bins) + bin_count: Final[int] = len(bins) + + if (min_bin != 1) or ((max_bin - min_bin + 1) != bin_count): + raise ValueError( + f"Inconsistent use of bins. Found bins {sorted(bins)}.") + + if not isinstance(x.n_bins, int): + raise type_error(x.n_bins, "x.n_bins", int) + if x.n_bins != bin_count: + raise ValueError(f"x.n_bins={x.n_bins}, but must be {bin_count}.")
+ + +
+[docs] + def n_points(self) -> int: + """ + Get the number of possible packings. + + If we indeed consider that any object could be at any place, then + there would be an incomprehensibly large number of possible packings. + Here, we consider the bottom-left condition and the idea of encoding + packings as signed permutations, as in the Liu and Teng paper "An + Improved BL-Algorithm for Genetic Algorithm of the Orthogonal Packing + of Rectangles." In this case, if `n` items are to be packed, the + number of possible packings won't exceed `2^n * n!`. + + :return: the approximate number of packings + + >>> inst = Instance.from_resource("a01") + >>> inst.n_items + 24 + >>> space = PackingSpace(inst) + >>> space.n_points() + 10409396852733332453861621760000 + >>> from math import factorial + >>> (2 ** 24) * factorial(24) + 10409396852733332453861621760000 + """ + return (2 ** self.instance.n_items) * factorial(self.instance.n_items)
+ + + def __str__(self) -> str: + """ + Get the name of the packing space. + + :return: the name + + >>> print(PackingSpace(Instance.from_resource("a10"))) + pack_a10 + """ + return f"pack_{self.instance}" + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log the parameters of the space to the given logger. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + with logger.scope(SCOPE_INSTANCE) as kv: + self.instance.log_parameters_to(kv)
+
+ +
diff --git a/_modules/moptipyapps/binpacking2d/packing_statistics.html b/_modules/moptipyapps/binpacking2d/packing_statistics.html new file mode 100644 index 00000000..a100c99b --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/packing_statistics.html @@ -0,0 +1,653 @@ +moptipyapps.binpacking2d.packing_statistics — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.packing_statistics

+"""An extended end result statistics record to represent packings."""
+import argparse
+import os.path
+from dataclasses import dataclass
+from math import isfinite
+from typing import Any, Callable, Final, Iterable, Mapping, cast
+
+from moptipy.evaluation.base import (
+    KEY_N,
+    EvaluationDataElement,
+)
+from moptipy.evaluation.end_results import EndResult
+from moptipy.evaluation.end_statistics import CsvReader as EsCsvReader
+from moptipy.evaluation.end_statistics import CsvWriter as EsCsvWriter
+from moptipy.evaluation.end_statistics import (
+    EndStatistics,
+)
+from moptipy.evaluation.end_statistics import (
+    from_end_results as es_from_end_results,
+)
+from moptipy.utils.strings import (
+    num_to_str,
+)
+from pycommons.ds.immutable_map import immutable_mapping
+from pycommons.ds.sequences import reiterable
+from pycommons.io.console import logger
+from pycommons.io.csv import (
+    SCOPE_SEPARATOR,
+    csv_column,
+    csv_read,
+    csv_scope,
+    csv_select_scope,
+    csv_write,
+)
+from pycommons.io.path import Path, file_path, line_writer
+from pycommons.math.sample_statistics import CsvReader as SsCsvReader
+from pycommons.math.sample_statistics import CsvWriter as SsCsvWriter
+from pycommons.math.sample_statistics import SampleStatistics
+from pycommons.math.sample_statistics import from_samples as ss_from_samples
+from pycommons.strings.string_conv import str_to_num
+from pycommons.types import check_int_range, type_error
+
+from moptipyapps.binpacking2d.objectives.bin_count import BIN_COUNT_NAME
+from moptipyapps.binpacking2d.packing_result import (
+    _OBJECTIVE_LOWER,
+    _OBJECTIVE_UPPER,
+    KEY_BIN_HEIGHT,
+    KEY_BIN_WIDTH,
+    KEY_N_DIFFERENT_ITEMS,
+    KEY_N_ITEMS,
+    LOWER_BOUNDS_BIN_COUNT,
+    PackingResult,
+)
+from moptipyapps.binpacking2d.packing_result import from_csv as pr_from_csv
+from moptipyapps.binpacking2d.packing_result import from_logs as pr_from_logs
+from moptipyapps.shared import (
+    moptipyapps_argparser,
+    motipyapps_footer_bottom_comments,
+)
+
+
+
+[docs] +@dataclass(frozen=True, init=False, order=False, eq=False) +class PackingStatistics(EvaluationDataElement): + """ + An end statistics record of one run of one algorithm on one problem. + + This record provides the information of the outcome of one application of + one algorithm to one problem instance in an immutable way. + """ + + #: the original end statistics record + end_statistics: EndStatistics + #: the number of items in the instance + n_items: int + #: the number of different items in the instance + n_different_items: int + #: the bin width + bin_width: int + #: the bin height + bin_height: int + #: the objective values evaluated after the optimization + objectives: Mapping[str, SampleStatistics] + #: the bounds for the objective values (append ".lowerBound" and + #: ".upperBound" to all objective function names) + objective_bounds: Mapping[str, int | float] + #: the bounds for the minimum number of bins of the instance + bin_bounds: Mapping[str, int] + + def __init__(self, + end_statistics: EndStatistics, + n_items: int, + n_different_items: int, + bin_width: int, + bin_height: int, + objectives: Mapping[str, SampleStatistics], + objective_bounds: Mapping[str, int | float], + bin_bounds: Mapping[str, int]): + """ + Create a consistent instance of :class:`PackingStatistics`. + + :param end_statistics: the end statistics + :param n_items: the number of items + :param n_different_items: the number of different items + :param bin_width: the bin width + :param bin_height: the bin height + :param objectives: the objective values computed after the + optimization + :param bin_bounds: the different bounds for the number of bins + :param objective_bounds: the bounds for the objective functions + :raises TypeError: if any parameter has a wrong type + :raises ValueError: if the parameter values are inconsistent + """ + super().__init__() + if not isinstance(end_statistics, EndStatistics): + raise type_error(end_statistics, "end_statistics", EndResult) + if end_statistics.best_f != objectives[end_statistics.objective]: + raise ValueError( + f"end_statistics.best_f={end_statistics.best_f}, but " + f"objectives[{end_statistics.objective!r}]=" + f"{objectives[end_statistics.objective]}.") + if not isinstance(objectives, Mapping): + raise type_error(objectives, "objectives", Mapping) + if not isinstance(objective_bounds, Mapping): + raise type_error(objective_bounds, "objective_bounds", Mapping) + if not isinstance(bin_bounds, Mapping): + raise type_error(bin_bounds, "bin_bounds", Mapping) + if len(objective_bounds) != (2 * len(objectives)): + raise ValueError(f"it is required that there is a lower and an " + f"upper bound for each of the {len(objectives)} " + f"functions, but we got {len(objective_bounds)} " + f"bounds, objectives={objectives}, " + f"objective_bounds={objective_bounds}.") + + for name, stat in objectives.items(): + if not isinstance(name, str): + raise type_error( + name, f"name of evaluation[{name!r}]={stat!r}", str) + if not isinstance(stat, int | float | SampleStatistics): + raise type_error( + stat, f"value of evaluation[{name!r}]={stat!r}", + (int, float, SampleStatistics)) + lll: str = csv_scope(name, _OBJECTIVE_LOWER) + lower = objective_bounds[lll] + if not isfinite(lower): + raise ValueError(f"{lll}=={lower}.") + uuu = csv_scope(name, _OBJECTIVE_UPPER) + upper = objective_bounds[uuu] + for value in (stat.minimum, stat.maximum) \ + if isinstance(stat, SampleStatistics) else (stat, ): + if not isfinite(value): + raise ValueError( + f"non-finite value of evaluation[{name!r}]={value!r}") + if not (lower <= value <= upper): + raise ValueError( + f"it is required that {lll}<=f<={uuu}, but got " + f"{lower}, {value}, and {upper}.") + bins: Final[SampleStatistics | None] = cast( + SampleStatistics, objectives[BIN_COUNT_NAME]) \ + if BIN_COUNT_NAME in objectives else None + for name2, value2 in bin_bounds.items(): + if not isinstance(name2, str): + raise type_error( + name2, f"name of bounds[{name2!r}]={value2!r}", str) + check_int_range(value2, f"bounds[{name2!r}]", 1, 1_000_000_000) + if (bins is not None) and (bins.minimum < value2): + raise ValueError( + f"number of bins={bins} is inconsistent with " + f"bound {name2!r}={value2}.") + + object.__setattr__(self, "end_statistics", end_statistics) + object.__setattr__(self, "objectives", immutable_mapping(objectives)) + object.__setattr__(self, "objective_bounds", + immutable_mapping(objective_bounds)) + object.__setattr__(self, "bin_bounds", immutable_mapping(bin_bounds)) + object.__setattr__(self, "n_different_items", check_int_range( + n_different_items, "n_different_items", 1, 1_000_000_000_000)) + object.__setattr__(self, "n_items", check_int_range( + n_items, "n_items", n_different_items, 1_000_000_000_000)) + object.__setattr__(self, "bin_width", check_int_range( + bin_width, "bin_width", 1, 1_000_000_000_000)) + object.__setattr__(self, "bin_height", check_int_range( + bin_height, "bin_height", 1, 1_000_000_000_000)) + + def _tuple(self) -> tuple[Any, ...]: + """ + Create a tuple with all the data of this data class for comparison. + + :returns: a tuple with all the data of this class, where `None` values + are masked out + """ + # noinspection PyProtectedMember + return self.end_statistics._tuple()
+ + + +
+[docs] +def from_packing_results( + results: Iterable[PackingResult], + consumer: Callable[[PackingStatistics], None]) -> None: + """ + Create packing statistics from a sequence of packing results. + + :param results: the packing results + :param consumer: the consumer receiving the created packing + statistics + """ + if not isinstance(results, Iterable): + raise type_error(results, "results", Iterable) + if not callable(consumer): + raise type_error(consumer, "consumer", call=True) + groups: Final[dict[tuple[str, str, str, str], list[PackingResult]]] \ + = {} + objectives_set: set[str] = set() + for i, pr in enumerate(results): + if not isinstance(pr, PackingResult): + raise type_error(pr, f"end_results[{i}]", PackingResult) + setting: tuple[str, str, str, str] = \ + (pr.end_result.algorithm, pr.end_result.instance, + pr.end_result.objective, "" if pr.end_result.encoding is None + else pr.end_result.encoding) + if setting in groups: + groups[setting].append(pr) + else: + groups[setting] = [pr] + objectives_set.update(pr.objectives.keys()) + + if len(groups) <= 0: + raise ValueError("results is empty!") + if len(objectives_set) <= 0: + raise ValueError("results has not objectives!") + end_stats: Final[list[EndStatistics]] = [] + objectives: Final[list[str]] = sorted(objectives_set) + + for key in sorted(groups.keys()): + data = groups[key] + pr0 = data[0] + n_items: int = pr0.n_items + n_different_items: int = pr0.n_different_items + bin_width: int = pr0.bin_width + bin_height: int = pr0.bin_height + used_objective: str = pr0.end_result.objective + encoding: str | None = pr0.end_result.encoding + if used_objective not in objectives_set: + raise ValueError( + f"{used_objective!r} not in {objectives_set!r}.") + if used_objective != key[2]: + raise ValueError( + f"used objective={used_objective!r} different " + f"from key[2]={key[2]}!?") + if (encoding is not None) and (encoding != key[3]): + raise ValueError( + f"used encoding={encoding!r} different " + f"from key[3]={key[3]}!?") + objective_bounds: Mapping[str, int | float] = pr0.objective_bounds + bin_bounds: Mapping[str, int] = pr0.bin_bounds + for i, pr in enumerate(data): + if n_items != pr.n_items: + raise ValueError(f"n_items={n_items} for data[0] but " + f"{pr.n_items} for data[{i}]?") + if n_different_items != pr.n_different_items: + raise ValueError( + f"n_different_items={n_different_items} for data[0] " + f"but {pr.n_different_items} for data[{i}]?") + if bin_width != pr.bin_width: + raise ValueError( + f"bin_width={bin_width} for data[0] " + f"but {pr.bin_width} for data[{i}]?") + if bin_height != pr.bin_height: + raise ValueError( + f"bin_height={bin_height} for data[0] " + f"but {pr.bin_height} for data[{i}]?") + if used_objective != pr.end_result.objective: + raise ValueError( + f"used objective={used_objective!r} for data[0] " + f"but {pr.end_result.objective!r} for data[{i}]?") + if objective_bounds != pr.objective_bounds: + raise ValueError( + f"objective_bounds={objective_bounds!r} for data[0] " + f"but {pr.objective_bounds!r} for data[{i}]?") + if bin_bounds != pr.bin_bounds: + raise ValueError( + f"bin_bounds={bin_bounds!r} for data[0] " + f"but {pr.bin_bounds!r} for data[{i}]?") + + es_from_end_results((pr.end_result for pr in data), end_stats.append) + if len(end_stats) != 1: + raise ValueError(f"got {end_stats} from {data}?") + + consumer(PackingStatistics( + end_statistics=end_stats[0], + n_items=n_items, + n_different_items=n_different_items, + bin_width=bin_width, + bin_height=bin_height, + objectives={ + o: ss_from_samples(pr.objectives[o] for pr in data) + for o in objectives + }, + objective_bounds=objective_bounds, + bin_bounds=bin_bounds, + )) + end_stats.clear()
+ + + +
+[docs] +def to_csv(results: Iterable[PackingStatistics], file: str) -> Path: + """ + Write a sequence of packing statistics to a file in CSV format. + + :param results: the end statistics + :param file: the path + :return: the path of the file that was written + """ + path: Final[Path] = Path(file) + logger(f"Writing packing statistics to CSV file {path!r}.") + path.ensure_parent_dir_exists() + with path.open_for_write() as wt: + consumer: Final[Callable[[str], None]] = line_writer(wt) + for p in csv_write( + data=sorted(results), + setup=CsvWriter().setup, + column_titles=CsvWriter.get_column_titles, + get_row=CsvWriter.get_row, + header_comments=CsvWriter.get_header_comments, + footer_comments=CsvWriter.get_footer_comments, + footer_bottom_comments=CsvWriter.get_footer_bottom_comments): + consumer(p) + logger(f"Done writing packing statistics to CSV file {path!r}.") + return path
+ + + +
+[docs] +def from_csv(file: str) -> Iterable[PackingStatistics]: + """ + Load the packing statistics from a CSV file. + + :param file: the file to read from + :returns: the iterable with the packing statistics + """ + path: Final[Path] = file_path(file) + logger(f"Now reading CSV file {path!r}.") + with path.open_for_read() as rd: + yield from csv_read(rows=rd, + setup=CsvReader, + parse_row=CsvReader.parse_row) + logger(f"Done reading CSV file {path!r}.")
+ + + +
+[docs] +class CsvWriter: + """A class for CSV writing of :class:`PackingStatistics`.""" + + def __init__(self, scope: str | None = None) -> None: + """ + Initialize the csv writer. + + :param scope: the prefix to be pre-pended to all columns + """ + #: an optional scope + self.scope: Final[str | None] = ( + str.strip(scope)) if scope is not None else None + + #: has this writer been set up? + self.__setup: bool = False + #: the end statistics writer + self.__es: Final[EsCsvWriter] = EsCsvWriter(scope) + #: the bin bounds + self.__bin_bounds: list[str] | None = None + #: the objectives + self.__objectives: list[SsCsvWriter] | None = None + #: the objective names + self.__objective_names: tuple[str, ...] | None = None + #: the lower bound names + self.__objective_lb_names: tuple[str, ...] | None = None + #: the upper bound names + self.__objective_ub_names: tuple[str, ...] | None = None + +
+[docs] + def setup(self, data: Iterable[PackingStatistics]) -> "CsvWriter": + """ + Set up this csv writer based on existing data. + + :param data: the data to setup with + :returns: this writer + """ + if self.__setup: + raise ValueError("CSV writer has already been set up.") + self.__setup = True + + data = reiterable(data) + self.__es.setup(pr.end_statistics for pr in data) + + bin_bounds_set: Final[set[str]] = set() + objectives_set: Final[set[str]] = set() + for pr in data: + bin_bounds_set.update(pr.bin_bounds.keys()) + objectives_set.update(pr.objectives.keys()) + if set.__len__(bin_bounds_set) > 0: + self.__bin_bounds = sorted(bin_bounds_set) + if set.__len__(objectives_set) > 0: + p: Final[str | None] = self.scope + self.__objective_names = tuple(sorted(objectives_set)) + self.__objective_lb_names = tuple(csv_scope( + oxx, _OBJECTIVE_LOWER) for oxx in self.__objective_names) + self.__objective_ub_names = tuple(csv_scope( + oxx, _OBJECTIVE_UPPER) for oxx in self.__objective_names) + self.__objectives = [SsCsvWriter( + scope=csv_scope(p, k), n_not_needed=True, what_short=k, + what_long=f"objective function {k}").setup( + ddd.objectives[k] for ddd in data + ) for k in self.__objective_names] + + return self
+ + +
+[docs] + def get_column_titles(self) -> Iterable[str]: + """ + Get the column titles. + + :param dest: the destination string consumer + """ + p: Final[str | None] = self.scope + yield from self.__es.get_column_titles() + + yield csv_scope(p, KEY_BIN_HEIGHT) + yield csv_scope(p, KEY_BIN_WIDTH) + yield csv_scope(p, KEY_N_ITEMS) + yield csv_scope(p, KEY_N_DIFFERENT_ITEMS) + if self.__bin_bounds: + for b in self.__bin_bounds: + yield csv_scope(p, b) + if self.__objective_names and self.__objectives: + for i, o in enumerate(self.__objectives): + yield csv_scope(p, self.__objective_lb_names[i]) + yield from o.get_column_titles() + yield csv_scope(p, self.__objective_ub_names[i])
+ + +
+[docs] + def get_row(self, data: PackingStatistics) -> Iterable[str]: + """ + Render a single packing result record to a CSV row. + + :param data: the end result record + :returns: the iterable with the row text + """ + yield from self.__es.get_row(data.end_statistics) + yield repr(data.bin_height) + yield repr(data.bin_width) + yield repr(data.n_items) + yield repr(data.n_different_items) + if self.__bin_bounds: + for bb in self.__bin_bounds: + yield (repr(data.bin_bounds[bb]) + if bb in data.bin_bounds else "") + if self.__objective_names and self.__objectives: + lb: Final[tuple[str, ...] | None] = self.__objective_lb_names + ub: Final[tuple[str, ...] | None] = self.__objective_ub_names + for i, ob in enumerate(self.__objective_names): + if lb is not None: + ox = lb[i] + yield (num_to_str(data.objective_bounds[ox]) + if ox in data.objective_bounds else "") + yield from SsCsvWriter.get_optional_row( + self.__objectives[i], data.objectives.get(ob)) + if ub is not None: + ox = ub[i] + yield (num_to_str(data.objective_bounds[ox]) + if ox in data.objective_bounds else "")
+ + +
+[docs] + def get_header_comments(self) -> Iterable[str]: + """ + Get any possible header comments. + + :returns: the header comments + """ + return ("End Statistics of Bin Packing Experiments", + "See the description at the bottom of the file.")
+ + + + + + +
+ + + +
+[docs] +class CsvReader: + """A class for CSV parsing to get :class:`PackingStatistics`.""" + + def __init__(self, columns: dict[str, int]) -> None: + """ + Create a CSV parser for :class:`EndResult`. + + :param columns: the columns + """ + super().__init__() + #: the end result csv reader + self.__es: Final[EsCsvReader] = EsCsvReader(columns) + #: the index of the n-items column + self.__idx_n_items: Final[int] = csv_column(columns, KEY_N_ITEMS) + #: the index of the n different items column + self.__idx_n_different: Final[int] = csv_column( + columns, KEY_N_DIFFERENT_ITEMS) + #: the index of the bin width column + self.__idx_bin_width: Final[int] = csv_column( + columns, KEY_BIN_WIDTH) + #: the index of the bin height column + self.__idx_bin_height: Final[int] = csv_column( + columns, KEY_BIN_HEIGHT) + #: the indices for the objective bounds + self.__bin_bounds: Final[tuple[tuple[str, int], ...]] = \ + csv_select_scope( + lambda x: tuple(sorted(((k, v) for k, v in x.items()))), + columns, LOWER_BOUNDS_BIN_COUNT) + if tuple.__len__(self.__bin_bounds) <= 0: + raise ValueError("No bin bounds found?") + #: the objective bounds columns + self.__objective_bounds: Final[tuple[tuple[str, int], ...]] = \ + csv_select_scope( + lambda x: tuple(sorted(((k, v) for k, v in x.items()))), + columns, None, + skip_orig_key=lambda s: not str.endswith( + s, (_OBJECTIVE_LOWER, _OBJECTIVE_UPPER))) + n_bounds: Final[int] = tuple.__len__(self.__objective_bounds) + if n_bounds <= 0: + raise ValueError("No objective function bounds found?") + if (n_bounds & 1) != 0: + raise ValueError(f"Number of bounds {n_bounds} should be even.") + n_val: Final[tuple[tuple[str, int]]] = ((KEY_N, self.__es.idx_n), ) + #: the parsers for the per-objective statistics + self.__objectives: Final[tuple[tuple[str, SsCsvReader], ...]] = \ + tuple((ss, csv_select_scope(SsCsvReader, columns, ss, n_val)) + for ss in sorted({s[0] for s in (str.split( + kk[0], SCOPE_SEPARATOR) for kk in + self.__objective_bounds) if (list.__len__(s) > 1) + and (str.__len__(s[0]) > 0)})) + n_objectives: Final[int] = tuple.__len__(self.__objectives) + if n_objectives <= 0: + raise ValueError("No objectives found?") + if (2 * n_objectives) != n_bounds: + raise ValueError( + f"Number {n_objectives} of objectives " + f"inconsistent with number {n_bounds} of bounds.") + +
+[docs] + def parse_row(self, data: list[str]) -> PackingStatistics: + """ + Parse a row of data. + + :param data: the data row + :return: the end result statistics + """ + return PackingStatistics( + self.__es.parse_row(data), + int(data[self.__idx_n_items]), + int(data[self.__idx_n_different]), + int(data[self.__idx_bin_width]), + int(data[self.__idx_bin_height]), + {o: v.parse_row(data) for o, v in self.__objectives}, + {o: str_to_num(data[v]) for o, v in self.__objective_bounds}, + {o: int(data[v]) for o, v in self.__bin_bounds}, + )
+
+ + + +# Run packing-results to stat file if executed as script +if __name__ == "__main__": + parser: Final[argparse.ArgumentParser] = moptipyapps_argparser( + __file__, "Build an end-results statistics CSV file.", + "This program computes statistics over packing results") + def_src: str = "./evaluation/end_results.txt" + if not os.path.isfile(def_src): + def_src = "./results" + parser.add_argument( + "source", nargs="?", default=def_src, + help="either the directory with moptipy log files or the path to the " + "end-results CSV file", type=Path) + parser.add_argument( + "dest", type=Path, nargs="?", + default="./evaluation/end_statistics.txt", + help="the path to the end results statistics CSV file to be created") + args: Final[argparse.Namespace] = parser.parse_args() + + src_path: Final[Path] = args.source + packing_results: Final[list[PackingResult]] = [] + if src_path.is_file(): + logger(f"{src_path!r} identifies as file, load as end-results csv") + packing_results.extend(pr_from_csv(src_path)) + else: + logger(f"{src_path!r} identifies as directory, load it as log files") + pr_from_logs(src_path, packing_results.append) + + packing_stats: Final[list[PackingStatistics]] = [] + from_packing_results( + results=packing_results, consumer=packing_stats.append) + to_csv(packing_stats, args.dest) +
diff --git a/_modules/moptipyapps/binpacking2d/plot_packing.html b/_modules/moptipyapps/binpacking2d/plot_packing.html new file mode 100644 index 00000000..bdf83523 --- /dev/null +++ b/_modules/moptipyapps/binpacking2d/plot_packing.html @@ -0,0 +1,219 @@ +moptipyapps.binpacking2d.plot_packing — moptipyapps 0.8.62 documentation

Source code for moptipyapps.binpacking2d.plot_packing

+"""Plot a packing into one figure."""
+from collections import Counter
+from typing import Callable, Final, Iterable
+
+import moptipy.utils.plot_defaults as pd
+import moptipy.utils.plot_utils as pu
+from matplotlib.artist import Artist  # type: ignore
+from matplotlib.axes import Axes  # type: ignore
+from matplotlib.figure import Figure  # type: ignore
+from matplotlib.patches import Rectangle  # type: ignore
+from matplotlib.text import Text  # type: ignore
+from pycommons.types import type_error
+
+from moptipyapps.binpacking2d.packing import (
+    IDX_BIN,
+    IDX_BOTTOM_Y,
+    IDX_ID,
+    IDX_LEFT_X,
+    IDX_RIGHT_X,
+    IDX_TOP_Y,
+    Packing,
+)
+
+
+
+[docs] +def default_packing_item_str(item_id: int, item_index: int, + item_in_bin_index: int) -> Iterable[str]: + """ + Get a packing item string(s). + + The default idea is to include the item id, the index of the item in the + bin, and the overall index of the item. If the space is insufficient, + we remove the latter or the latter two. Hence, this function returns a + tuple of three strings. + + :param item_id: the ID of the packing item + :param item_index: the item index + :param item_in_bin_index: the index of the item in its bin + :return: the string + """ + return (f"{item_id}/{item_in_bin_index}/{item_index}", + f"{item_id}/{item_in_bin_index}", str(item_id))
+ + + +
+[docs] +def plot_packing(packing: Packing | str, + max_rows: int = 3, + max_bins_per_row: int = 3, + default_width_per_bin: float | int | None = 8.6, + max_width: float | int | None = 8.6, + default_height_per_bin: float | int | None = + 5.315092303249095, + max_height: float | int | None = 9, + packing_item_str: Callable[ + [int, int, int], str | Iterable[str]] = + default_packing_item_str, + importance_to_font_size_func: Callable[[int], float] = + pd.importance_to_font_size, + dpi: float | int | None = 384.0) -> Figure: + """ + Plot a packing. + + Each item is drawn in a different color. Each item rectangle includes, if + there is enough space, the item-ID. If there is more space, also the index + of the item inside the bin (starting at 1) is included. If there is yet + more space, even the overall index of the item is included. + + :param packing: the packing or the file to load it from + :param max_rows: the maximum number of rows + :param max_bins_per_row: the maximum number of bins per row + :param default_width_per_bin: the optional default width of a column + :param max_height: the maximum height + :param default_height_per_bin: the optional default height per row + :param max_width: the maximum width + :param packing_item_str: the function converting an item id, + item index, and item-in-bin index to a string or sequence of strings + (of decreasing length) + :param importance_to_font_size_func: the function converting + importance values to font sizes + :param dpi: the dpi value + :returns: the Figure object to allow you to add further plot elements + """ + if isinstance(packing, str): + packing = Packing.from_log(packing) + if not isinstance(packing, Packing): + raise type_error(packing, "packing", (Packing, str)) + if not callable(packing_item_str): + raise type_error(packing_item_str, "packing_item_str", call=True) + + # allocate the figure ... this is hacky for now + figure, bin_figures = pu.create_figure_with_subplots( + items=packing.n_bins, max_items_per_plot=1, max_rows=max_rows, + max_cols=max_bins_per_row, min_rows=1, min_cols=1, + default_width_per_col=default_width_per_bin, max_width=max_width, + default_height_per_row=default_height_per_bin, max_height=max_height, + dpi=dpi) + + # initialize the different plots + bin_width: Final[int] = packing.instance.bin_width + bin_height: Final[int] = packing.instance.bin_height + axes_list: Final[list[Axes]] = [] + for the_axes, _, _, _, _, _ in bin_figures: + axes = pu.get_axes(the_axes) + axes_list.append(axes) + axes.set_ylim(0, bin_width) # pylint: disable=E1101 + axes.set_ybound(0, bin_height) # pylint: disable=E1101 + axes.set_xlim(0, bin_width) # pylint: disable=E1101 + axes.set_xbound(0, bin_width) # pylint: disable=E1101 + axes.set_aspect("equal", None, "C") # pylint: disable=E1101 + axes.tick_params( # pylint: disable=E1101 + left=False, bottom=False, labelleft=False, labelbottom=False) + + # get the color and font styles + colors: Final[tuple] = pd.distinct_colors( + packing.instance.n_different_items) + font_size: Final[float] = importance_to_font_size_func(-1) + + # get the transforms needed to obtain text dimensions + renderers: Final[list] = [pu.get_renderer(axes) for axes in axes_list] + inverse: Final[list] = [ + axes.transData.inverted() # type: ignore # pylint: disable=E1101 + for axes in axes_list] + + z_order: int = 0 # the z-order of all drawing elements + + # we now plot the items one-by-one + bin_counters: Counter[int] = Counter() + for item_index in range(packing.instance.n_items): + item_id: int = int(packing[item_index, IDX_ID]) + item_bin: int = int(packing[item_index, IDX_BIN]) + x_left: int = int(packing[item_index, IDX_LEFT_X]) + y_bottom: int = int(packing[item_index, IDX_BOTTOM_Y]) + x_right: int = int(packing[item_index, IDX_RIGHT_X]) + y_top: int = int(packing[item_index, IDX_TOP_Y]) + item_in_bin_index: int = bin_counters[item_bin] + 1 + bin_counters[item_bin] = item_in_bin_index + width: int = x_right - x_left + height: int = y_top - y_bottom + + background = colors[item_id - 1] + foreground = pd.text_color_for_background(colors[item_id - 1]) + + axes = axes_list[item_bin - 1] + rend = renderers[item_bin - 1] + inv = inverse[item_bin - 1] + + axes.add_artist(Rectangle( # paint the item's rectangle + xy=(x_left, y_bottom), width=width, height=height, + facecolor=background, linewidth=0.75, zorder=z_order, + edgecolor="black")) + z_order += 1 + x_center: float = 0.5 * (x_left + x_right) + y_center: float = 0.5 * (y_bottom + y_top) + +# get the box label string or string sequence + strs = packing_item_str(item_id, item_index + 1, item_in_bin_index) + if isinstance(strs, str): + strs = [strs] + elif not isinstance(strs, Iterable): + raise type_error( + strs, f"packing_item_str({item_id}, {item_index}, " + f"{item_in_bin_index})", (str, Iterable)) + +# iterate over the possible box label strings + for i, item_str in enumerate(strs): + if not isinstance(item_str, str): + raise type_error( + str, f"packing_item_str({item_id}, {item_index}, " + f"{item_in_bin_index})[{i}]", str) +# Get the size of the text using a temporary text that gets immediately +# deleted again. + tmp: Text = axes.text(x=x_center, y=y_center, s=item_str, + fontsize=font_size, + color=foreground, + horizontalalignment="center", + verticalalignment="baseline") + bb_bl = inv.transform_bbox(tmp.get_window_extent( + renderer=rend)) + Artist.set_visible(tmp, False) + Artist.remove(tmp) + del tmp + +# Check if this text did fit into the rectangle. + if (bb_bl.width < (0.97 * width)) and \ + (bb_bl.height < (0.97 * height)): + # OK, there is enough space. Let's re-compute the y offset + # to do proper alignment using another temporary text. + tmp = axes.text(x=x_center, y=y_center, s=item_str, + fontsize=font_size, + color=foreground, + horizontalalignment="center", + verticalalignment="bottom") + bb_bt = inv.transform_bbox(tmp.get_window_extent( + renderer=rend)) + Artist.set_visible(tmp, False) + Artist.remove(tmp) + del tmp + +# Now we can really print the actual text with a more or less nice vertical +# alignment. + adj = bb_bl.y0 - bb_bt.y0 + if adj < 0: + y_center += adj / 3 + + axes.text(x=x_center, y=y_center, s=item_str, + fontsize=font_size, + color=foreground, + horizontalalignment="center", + verticalalignment="center", + zorder=z_order) + z_order += 1 + break # We found a text that fits, so we can quit. + return figure
+ +
diff --git a/_modules/moptipyapps/dynamic_control/.nojekyll b/_modules/moptipyapps/dynamic_control/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/moptipyapps/dynamic_control/controller.html b/_modules/moptipyapps/dynamic_control/controller.html new file mode 100644 index 00000000..9c284ab2 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/controller.html @@ -0,0 +1,118 @@ +moptipyapps.dynamic_control.controller — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.controller

+"""
+A base class for implementing controllers.
+
+A controller basically is a parameterizable function that receives the current
+state and time of a :mod:`~moptipyapps.dynamic_control.system` as input and
+computes one or multiple controller values as output. These controller values
+are then used to influence how the state of the system changes in the next
+iteration. In the dynamic systems control optimization task, the goal is to
+find the right parameterization for the controller such that an
+:mod:`~moptipyapps.dynamic_control.objective` is minimized.
+
+Examples for different controllers for dynamic systems are given in package
+:mod:`~moptipyapps.dynamic_control.controllers`.
+"""
+
+
+from typing import Callable, Final
+
+import numpy as np
+from moptipy.api.component import Component
+from moptipy.spaces.vectorspace import VectorSpace
+from moptipy.utils.logger import KeyValueLogSection
+from moptipy.utils.strings import sanitize_name
+from pycommons.types import check_to_int_range, type_error
+
+
+
+[docs] +class Controller(Component): + """A class for governing a system via differential equations.""" + + def __init__(self, name: str, + state_dims: int, control_dims: int, param_dims: int, + func: Callable[[np.ndarray, float, np.ndarray, + np.ndarray], None] | None = None) -> None: + """ + Initialize the system. + + :param name: the name of the system. + :param state_dims: the state dimensions + :param control_dims: the control dimensions + :param param_dims: the parameter dimensions + """ + super().__init__() + if not isinstance(name, str): + raise type_error(name, "name", str) + nn: Final[str] = sanitize_name(name) + if nn != name: + raise ValueError( + f"sanitized name {nn!r} is different from name {name!r}.") + #: the controller name + self.name: Final[str] = name + #: the dimensions of the state variable + self.state_dims: Final[int] = check_to_int_range( + state_dims, "state_dims", 2, 100) + #: the dimensions of the controller output + self.control_dims: Final[int] = check_to_int_range( + control_dims, "control_dims", 1, 100) + #: the dimensions of the controller parameter + self.param_dims: Final[int] = check_to_int_range( + param_dims, "param_dims", 1, 1_000) + if func is not None: + if not callable(func): + raise type_error(func, "func", None, call=True) + self.controller = func # type: ignore + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log all parameters of this component as key-value pairs. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + logger.key_value("stateDims", self.state_dims) + logger.key_value("controlDims", self.control_dims) + logger.key_value("paramDims", self.param_dims)
+ + + def __str__(self): + """ + Get the name of this controller. + + :return: the name of this controller + """ + return self.name + +
+[docs] + def controller(self, state: np.ndarray, # pylint: disable=E0202 + time: float, # pylint: disable=E0202 + params: np.ndarray, # pylint: disable=E0202 + out: np.ndarray) -> None: # pylint: disable=E0202 + """ + Compute the control value and store it in `out`. + + :param state: the state vector + :param time: the time value + :param params: the controller variables + :param out: the output array to receive the controller values + """
+ + +
+[docs] + def parameter_space(self) -> VectorSpace: + """ + Create a vector space to represent the possible parameterizations. + + :return: a vector space for the possible parameterizations of this + controller. + """ + return VectorSpace(self.param_dims, -32.0, 32.0)
+
+ +
diff --git a/_modules/moptipyapps/dynamic_control/controllers/.nojekyll b/_modules/moptipyapps/dynamic_control/controllers/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/moptipyapps/dynamic_control/controllers/ann.html b/_modules/moptipyapps/dynamic_control/controllers/ann.html new file mode 100644 index 00000000..c3ea39cb --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/controllers/ann.html @@ -0,0 +1,140 @@ +moptipyapps.dynamic_control.controllers.ann — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.controllers.ann

+"""
+Poor man's Artificial Neural Networks.
+
+Here, artificial neural networks (ANNs) are defined as plain mathematical
+functions which are parameterized by their weights. The weights are subject
+to black-box optimization and all together put into a single vector.
+In other words, we do not use proper back-propagation learning or any other
+sophisticated neural network specific training strategy. Instead, we treat the
+neural networks as black boxes that can be parameterized using the weight
+vector. Different ANN architectures have different weight vectors.
+As activation functions, we use `arctan`.
+
+The neural networks here are automatically generated via code generation
+provided by module :mod:`~moptipyapps.dynamic_control.controllers.codegen`.
+This allows us to have networks of arbitrary shape for arbitrary input and
+output dimensions. Since code generation followed by on-the-fly compilation
+via numba is time and memory consuming, we cache all neural network-based
+instances of :class:`~moptipyapps.dynamic_control.controller.Controller`.
+"""
+
+from typing import Callable, Final, Iterable, cast
+
+import numpy as np
+from pycommons.types import check_int_range, type_error
+
+from moptipyapps.dynamic_control.controller import Controller
+from moptipyapps.dynamic_control.controllers.codegen import CodeGenerator
+from moptipyapps.dynamic_control.system import System
+
+
+
+[docs] +def make_ann(state_dims: int, control_dims: int, layers: list[int]) \ + -> Controller: + """ + Dynamically create an ANN. + + :param state_dims: the state or input dimension + :param control_dims: the output dimension + :param layers: the sizes of the hidden layers + :returns: the controller + """ + state_dims = check_int_range(state_dims, "state_dims", 1, 100) + control_dims = check_int_range(control_dims, "state_dims", 1, 100) + if not isinstance(layers, list): + raise type_error(layers, "layers", list) + for layer in layers: + check_int_range(layer, "layer", 1, 64) + + # we also try to cache the generated controllers + description = "_".join(map(str, ([state_dims, control_dims, *layers]))) + description = f"__cache_{description}" + if hasattr(make_ann, description): + return cast(Controller, getattr(make_ann, description)) + + code: Final[CodeGenerator] = CodeGenerator( + "state: np.ndarray, _: float, params: np.ndarray, out: np.ndarray") + params: int = 0 # the number of parameters + var_count: int = 0 # the number of variables + vars_in: list[str] = [] # the variables forming the current layer input + vars_out: list[str] = [] # the variables forming the current layer output + vars_cached: list[str] = [] # the variables cached for re-use + write: Final[Callable[[str], None]] = code.write # fast call + writeln: Final[Callable[[str], None]] = code.writeln # fast call + + # first, we cache the state vector into local variables + for i in range(state_dims): + vv = f"s{i}" + vars_in.append(vv) + writeln(f"{vv} = state[{i}]") + + # now we build the hidden layers of the network + for layer in layers: + for _ in range(layer): + # allocate a variable for storing the current neuron's output + if len(vars_cached) > 0: + var = vars_cached.pop(-1) + else: + var_count += 1 + var = f"v{var_count}" + vars_out.append(var) # remember the variable + write(f"{var} = np.arctan(params[{params}]") # the bias + params += 1 + for vv in vars_in: + write(f" + params[{params}] * {vv}") # input * weight + params += 1 + writeln(")") + vars_cached.extend(vars_in) # old inputs ready for reuse + vars_in.clear() # inputs are no longer used + vars_in, vars_out = vars_out, vars_in # outputs become inputs + + # now we construct the output layer + for i in range(control_dims): + write(f"out[{i}] = params[{params}] * ") # the multiplier + params += 1 + write(f"np.arctan(params[{params}]") # the bias + params += 1 + for vv in vars_in: + write(f" + params[{params}] * {vv}") # input * weight + params += 1 + writeln(")") + + result: Final[Controller] = Controller( + f"ann_{'_'.join(map(str, layers))}" if len(layers) > 0 else "ann", + state_dims, control_dims, params, code.build()) + # perform one test invocation + result.controller(np.zeros(state_dims), 0.0, np.zeros(params), + np.empty(control_dims)) + setattr(make_ann, description, result) # cache the controller + return result
+ + + +
+[docs] +def anns(system: System) -> Iterable[Controller]: + """ + Create poor man's ANNs fitting to a given system. + + Based on the dimensionality of the state space, we generate a set of ANNs + with different numbers of layers and neurons. The weights of the neurons + can then directly be optimized by a numerical optimization algorithm. + This is, of course, probably much less efficient that doing some proper + learning like back-propagation. However, it allows us to easily plug the + ANNs into the same optimization routines as other controllers. + + :param system: the equations object + :return: the ANNs + """ + state_dims: Final[int] = system.state_dims + control_dims: Final[int] = system.control_dims + return (make_ann(state_dims, control_dims, []), + make_ann(state_dims, control_dims, [1]), + make_ann(state_dims, control_dims, [2]), + make_ann(state_dims, control_dims, [3]), + make_ann(state_dims, control_dims, [2, 2]), + make_ann(state_dims, control_dims, [3, 2]))
+ +
diff --git a/_modules/moptipyapps/dynamic_control/controllers/codegen.html b/_modules/moptipyapps/dynamic_control/controllers/codegen.html new file mode 100644 index 00000000..ec1e5427 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/controllers/codegen.html @@ -0,0 +1,121 @@ +moptipyapps.dynamic_control.controllers.codegen — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.controllers.codegen

+"""A simple code generator."""
+
+from io import StringIO
+from typing import Any, Callable, Final
+
+import numba  # type: ignore
+import numpy as np
+from pycommons.types import type_error
+
+
+
+[docs] +class CodeGenerator: + """A simple code generator.""" + + def __init__(self, args: str = "", retval: str = "None", + fastmath: bool = True) -> None: + """ + Initialize the code generator. + + :param args: the command line arguments + :param retval: the return type + :param fastmath: do we use numba fast math? + """ + if not isinstance(args, str): + raise type_error(args, "args", str) + if not isinstance(retval, str): + raise type_error(retval, "retval", str) + if not isinstance(fastmath, bool): + raise type_error(fastmath, "fastmath", bool) + + io: Final[StringIO] = StringIO() + wrt: Final[Callable[[str], int]] = io.write + + #: the callable for writing + self.__write: Final[Callable[[str], int]] = wrt + wrt("@numba.njit(cache=False, inline='always', fastmath" + f"={fastmath}, boundscheck=False)\n") + wrt(f"def ____func({args}) -> {retval}:\n") + + #: the result callable + self.__res: Final[Callable[[], str]] = io.getvalue + #: the current indent + self.__indent: int = 1 + #: are we at the start of a line? + self.__start: bool = True + +
+[docs] + def write(self, text: str) -> None: + """ + Write some code. + + :param text: the code text + """ + if self.__start: + self.__write(self.__indent * " ") + self.__start = False + self.__write(text)
+ + +
+[docs] + def indent(self) -> None: + """Increase the indent.""" + self.__indent += 1
+ + +
+[docs] + def unindent(self) -> None: + """Increase the indent.""" + self.__indent -= 1 + if self.__indent <= 0: + raise ValueError("indent becomes 0, not allowed")
+ + +
+[docs] + def writeln(self, text: str = "") -> None: + """ + End a line. + + :param text: the text to be written + """ + self.write(text) + self.endline()
+ + +
+[docs] + def endline(self) -> None: + """End a line.""" + if not self.__start: + self.__write("\n") + self.__start = True
+ + +
+[docs] + def build(self) -> Callable: + """ + Compile the generated code. + + :returns: the generated function + """ + loca: Final[dict[str, Any]] = {} + self.endline() + code: Final[str] = self.__res() + globs: Final[dict[str, Any]] = {"numba": numba, "np": np, + "Final": Final} + # pylint: disable=W0122 + exec(code, globs, loca) # nosec # nosemgrep # noqa + res = loca["____func"] + if not callable(res): + raise type_error(res, f"compiled {code!r}", call=True) + return res
+
+ +
diff --git a/_modules/moptipyapps/dynamic_control/controllers/cubic.html b/_modules/moptipyapps/dynamic_control/controllers/cubic.html new file mode 100644 index 00000000..379b308a --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/controllers/cubic.html @@ -0,0 +1,89 @@ +moptipyapps.dynamic_control.controllers.cubic — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.controllers.cubic

+"""
+A cubic controller.
+
+A cubic controller is a function where all value of the state vector enter
+the computation plainly, squared, and raised to the third power. The powers
+of their combinations do not exceed the third power, e.g., the controller is
+a linear combination of A, B, A², AB, B², A³, A²B, B²A, and B³ if the state
+has values A and B. The controller represents the multipliers for these
+coefficients.
+"""
+
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+
+from moptipyapps.dynamic_control.controller import Controller
+from moptipyapps.dynamic_control.system import System
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __cubic_2d_1o(state: np.ndarray, _: float,
+                  params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a cubic polynomial for 2d state spaces.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+    s02: Final[float] = s0 * s0
+    s12: Final[float] = s1 * s1
+    out[0] = (s0 * params[0]) + (s1 * params[1]) \
+        + (s02 * params[2]) + (s0 * s1 * params[3]) + (s12 * params[4]) \
+        + (s02 * s0 * params[5]) + (s02 * s1 * params[6]) \
+        + (s0 * s12 * params[7]) + (s12 * s1 * params[8])
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __cubic_3d_1o(state: np.ndarray, _: float,
+                  params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a cubic polynomial for 3d state spaces.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+    s2: Final[float] = state[2]
+    s02: Final[float] = s0 * s0
+    s12: Final[float] = s1 * s1
+    s22: Final[float] = s2 * s2
+    out[0] = (s0 * params[0]) + (s1 * params[1]) + (s2 * params[2]) \
+        + (s02 * params[3]) + (s0 * s1 * params[4]) \
+        + (s0 * s2 * params[5]) + (s12 * params[6]) \
+        + (s1 * s2 * params[7]) + (s22 * params[8]) \
+        + (s0 * s02 * params[9]) + (s02 * s1 * params[10]) \
+        + (s02 * s2 * params[11]) + (s12 * s1 * params[12]) \
+        + (s12 * s0 * params[13]) + (s12 * s2 * params[14]) \
+        + (s22 * s2 * params[15]) + (s22 * s0 * params[16]) \
+        + (s22 * s1 * params[17])
+
+
+
+[docs] +def cubic(system: System) -> Controller: + """ + Create a cubic controller for the given equations object. + + :param system: the equations object + :return: the cubic controller + """ + if system.control_dims != 1: + raise ValueError("invalid controller dimensions " + f"{system.control_dims} for {system!r}.") + name: Final[str] = "cubic" + if system.state_dims == 2: + return Controller(name, 2, 1, 9, __cubic_2d_1o) + if system.state_dims == 3: + return Controller(name, 3, 1, 19, __cubic_3d_1o) + raise ValueError("invalid state dimensions " + f"{system.state_dims} for {system!r}.")
+ +
diff --git a/_modules/moptipyapps/dynamic_control/controllers/linear.html b/_modules/moptipyapps/dynamic_control/controllers/linear.html new file mode 100644 index 00000000..15a5c682 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/controllers/linear.html @@ -0,0 +1,66 @@ +moptipyapps.dynamic_control.controllers.linear — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.controllers.linear

+"""
+A linear controller.
+
+In a linear controller, all values of the state vector enter only as-is,
+i.e., it is a linear combination of A and B if the state is composed of
+the two values A and B. We then optimize the weights of these coefficients.
+"""
+
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+
+from moptipyapps.dynamic_control.controller import Controller
+from moptipyapps.dynamic_control.system import System
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __linear_2d_1o(state: np.ndarray, _: float,
+                   params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a linear function for 2d state spaces.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    out[0] = (state[0] * params[0]) + (state[1] * params[1])
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __linear_3d_1o(state: np.ndarray, _: float,
+                   params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a linear function for 3d state spaces.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    out[0] = (state[0] * params[0]) + (state[1] * params[1]) \
+        + (state[2] * params[2])
+
+
+
+[docs] +def linear(system: System) -> Controller: + """ + Create a linear controller for the given equations object. + + :param system: the equations object + :return: the linear controller + """ + if system.control_dims != 1: + raise ValueError("invalid controller dimensions " + f"{system.control_dims} for {system!r}.") + name: Final[str] = "linear" + if system.state_dims == 2: + return Controller(name, 2, 1, 2, __linear_2d_1o) + if system.state_dims == 3: + return Controller(name, 3, 1, 3, __linear_3d_1o) + raise ValueError("invalid state dimensions " + f"{system.state_dims} for {system!r}.")
+ +
diff --git a/_modules/moptipyapps/dynamic_control/controllers/min_ann.html b/_modules/moptipyapps/dynamic_control/controllers/min_ann.html new file mode 100644 index 00000000..17b5a0d4 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/controllers/min_ann.html @@ -0,0 +1,730 @@ +moptipyapps.dynamic_control.controllers.min_ann — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.controllers.min_ann

+"""
+Poor man's Artificial Neural Networks with minimized input.
+
+ANNs that include the state as input variable together with an additional
+variable, say `z`. The controller output is then the value `z*` for which
+the ANN takes on the smallest value (under the current state). In other
+words, the ANN is supposed to model the system's objective function. The
+idea is similar to :mod:`~moptipyapps.dynamic_control.controllers.ann`, but
+instead of using the output of the ANNs as controller values, we use the value
+`z*` for which the output of the ANN becomes minimal as controller value.
+"""
+
+from typing import Final, Iterable
+
+import numba  # type: ignore
+import numpy as np
+from numpy import inf
+
+from moptipyapps.dynamic_control.controller import Controller
+from moptipyapps.dynamic_control.system import System
+
+#: the golden ratio
+PHI: Final[float] = 0.5 * (np.sqrt(5.0) + 1.0)
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __min_ann_3d_1o_1(state: np.ndarray, _: float,
+                      params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Minimize a poor man's ANN for 3d-spaces with one layer containing 1 node.
+
+    We use :func:`numpy.arctan` as activation function. The output layer
+    is just a weighted sum of the hidden layer and no other transformation
+    is performed. The ANN has one additional parameter besides the `state`.
+    We then find the value of this parameter for which the ANN takes on the
+    smallest value via an iterated bracket - golden ratio search.
+
+    :param state: the current state of the system
+    :param params: the weight vector for the neurons.
+    :param out: the control vector, receiving one single element
+    """
+    hl_1_1_in: Final[float] = (state * params[0:3]).sum()
+    x_3: Final[float] = params[3]
+
+# first estimate of the best value
+    x_best: float = 0.0
+    f_best: float = np.arctan(hl_1_1_in + x_3 * x_best)
+
+# now do bracketing
+    x_low = x_a = -1000.0
+    f_a: float = np.arctan(hl_1_1_in + x_3 * x_a)
+    if f_a < f_best:
+        x_best = x_a
+        f_best = f_a
+
+    x_c = x_b = -990.0
+    f_c = f_b = np.arctan(hl_1_1_in + x_3 * x_b)
+    if f_b < f_best:
+        x_best = x_b
+        f_best = f_b
+
+# we try optimization in every single bracket we find
+    found_bracket: bool = True
+    ever_found_backet: bool = False
+    while found_bracket:
+
+        found_bracket = False
+        while x_b < 1000.0:
+            x_c = x_b + 10.0
+            f_c = np.arctan(hl_1_1_in + x_3 * x_c)
+            if f_c < f_best:
+                x_best = x_c
+                f_best = f_c
+            if (f_c > f_b) and (f_b < f_a):
+                ever_found_backet = found_bracket = True
+                break
+            x_a = x_b
+            f_a = f_b
+            x_b = x_c
+            f_b = f_c
+
+    # use huge range -1e3 ... 1e3
+        if found_bracket:
+            x_low = np.nextafter(x_a, inf)
+            x_high = np.nextafter(x_c, -inf)
+        elif ever_found_backet:
+            break
+        else:
+            x_high = x_c
+
+    # golden ratio algorithm
+        delta = x_high - x_low
+        while delta > 1e-12:
+            delta /= PHI
+            x_cc = x_high - delta
+
+            f_cc: float = np.arctan(hl_1_1_in + x_3 * x_cc)
+            if f_cc < f_best:
+                x_best = x_cc
+                f_best = f_cc
+
+            x_dd = x_low + delta
+            f_dd: float = np.arctan(hl_1_1_in + x_3 * x_dd)
+            if f_dd < f_best:
+                x_best = x_dd
+                f_best = f_dd
+
+            if f_cc < f_dd:
+                x_high = np.nextafter(x_dd, -inf)
+            else:
+                x_low = np.nextafter(x_cc, inf)
+            delta = x_high - x_low
+
+        # move forward to next bracket
+        x_a = x_b
+        f_a = f_b
+        x_b = x_c
+        f_b = f_c
+
+    out[0] = x_best
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __min_ann_2d_1o_1(state: np.ndarray, _: float,
+                      params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Minimize a poor man's ANN for 2d-spaces with one layer containing 1 node.
+
+    We use :func:`numpy.arctan` as activation function. The output layer
+    is just a weighted sum of the hidden layer and no other transformation
+    is performed. The ANN has one additional parameter besides the `state`.
+    We then find the value of this parameter for which the ANN takes on the
+    smallest value via an iterated bracket - golden ratio search.
+
+    :param state: the current state of the system
+    :param params: the weight vector for the neurons.
+    :param out: the control vector, receiving one single element
+    """
+    hl_1_1_in: Final[float] = (state * params[0:2]).sum()
+    x_2: Final[float] = params[2]
+
+# first estimate of the best value
+    x_best: float = 0.0
+    f_best: float = np.arctan(hl_1_1_in + x_2 * x_best)
+
+# now do bracketing
+    x_low = x_a = -1000.0
+    f_a: float = np.arctan(hl_1_1_in + x_2 * x_a)
+    if f_a < f_best:
+        x_best = x_a
+        f_best = f_a
+
+    x_c = x_b = -990.0
+    f_c = f_b = np.arctan(hl_1_1_in + x_2 * x_b)
+    if f_b < f_best:
+        x_best = x_b
+        f_best = f_b
+
+# we try optimization in every single bracket we find
+    found_bracket: bool = True
+    ever_found_backet: bool = False
+    while found_bracket:
+
+        found_bracket = False
+        while x_b < 1000.0:
+            x_c = x_b + 10.0
+            f_c = np.arctan(hl_1_1_in + x_2 * x_c)
+            if f_c < f_best:
+                x_best = x_c
+                f_best = f_c
+            if (f_c > f_b) and (f_b < f_a):
+                ever_found_backet = found_bracket = True
+                break
+            x_a = x_b
+            f_a = f_b
+            x_b = x_c
+            f_b = f_c
+
+    # use huge range -1e3 ... 1e3
+        if found_bracket:
+            x_low = np.nextafter(x_a, inf)
+            x_high = np.nextafter(x_c, -inf)
+        elif ever_found_backet:
+            break
+        else:
+            x_high = x_c
+
+    # golden ratio algorithm
+        delta = x_high - x_low
+        while delta > 1e-12:
+            delta /= PHI
+            x_cc = x_high - delta
+
+            f_cc: float = np.arctan(hl_1_1_in + x_2 * x_cc)
+            if f_cc < f_best:
+                x_best = x_cc
+                f_best = f_cc
+
+            x_dd = x_low + delta
+            f_dd: float = np.arctan(hl_1_1_in + x_2 * x_dd)
+            if f_dd < f_best:
+                x_best = x_dd
+                f_best = f_dd
+
+            if f_cc < f_dd:
+                x_high = np.nextafter(x_dd, -inf)
+            else:
+                x_low = np.nextafter(x_cc, inf)
+            delta = x_high - x_low
+
+        # move forward to next bracket
+        x_a = x_b
+        f_a = f_b
+        x_b = x_c
+        f_b = f_c
+
+    out[0] = x_best
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __min_ann_3d_1o_2(state: np.ndarray, _: float,
+                      params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Minimize a poor man's ANN for 3d-spaces with one layer containing 2 nodes.
+
+    We use :func:`numpy.arctan` as activation function. The output layer
+    is just a weighted sum of the hidden layer and no other transformation
+    is performed. The ANN has one additional parameter besides the `state`.
+    We then find the value of this parameter for which the ANN takes on the
+    smallest value via an iterated bracket - golden ratio search.
+
+    :param state: the current state of the system
+    :param params: the weight vector for the neurons.
+    :param out: the control vector, receiving one single element
+    """
+    hl_1_1_in: Final[float] = (state * params[0:3]).sum()
+    x_3: Final[float] = params[3]
+    x_4: Final[float] = params[4]
+    hl_1_2_in: Final[float] = (state * params[5:8]).sum()
+    x_8: Final[float] = params[8]
+    x_9: Final[float] = params[9]
+    x_10: Final[float] = params[10]
+    x_11: Final[float] = params[11]
+
+# first estimate of the best value
+    x_best: float = 0.0
+    hl_1_1: float = np.arctan(hl_1_1_in + x_3 * x_best + x_4)
+    hl_1_2: float = np.arctan(hl_1_2_in + x_8 * x_best + x_9)
+    f_best: float = hl_1_1 * x_10 + hl_1_2 * x_11
+
+# now do bracketing
+    x_low = x_a = -1000.0
+    hl_1_1 = np.arctan(hl_1_1_in + x_3 * x_a + x_4)
+    hl_1_2 = np.arctan(hl_1_2_in + x_8 * x_a + x_9)
+    f_a: float = hl_1_1 * x_10 + hl_1_2 * x_11
+    if f_a < f_best:
+        x_best = x_a
+        f_best = f_a
+
+    x_c = x_b = -990.0
+    hl_1_1 = np.arctan(hl_1_1_in + x_3 * x_b + x_4)
+    hl_1_2 = np.arctan(hl_1_2_in + x_8 * x_b + x_9)
+    f_c = f_b = hl_1_1 * x_10 + hl_1_2 * x_11
+    if f_b < f_best:
+        x_best = x_b
+        f_best = f_b
+
+# we try optimization in every single bracket we find
+    found_bracket: bool = True
+    ever_found_backet: bool = False
+    while found_bracket:
+
+        found_bracket = False
+        while x_b < 1000.0:
+            x_c = x_b + 10.0
+            hl_1_1 = np.arctan(hl_1_1_in + x_3 * x_c + x_4)
+            hl_1_2 = np.arctan(hl_1_2_in + x_8 * x_c + x_9)
+            f_c = hl_1_1 * x_10 + hl_1_2 * x_11
+            if f_c < f_best:
+                x_best = x_c
+                f_best = f_c
+            if (f_c > f_b) and (f_b < f_a):
+                ever_found_backet = found_bracket = True
+                break
+            x_a = x_b
+            f_a = f_b
+            x_b = x_c
+            f_b = f_c
+
+    # use huge range -1e3 ... 1e3
+        if found_bracket:
+            x_low = np.nextafter(x_a, inf)
+            x_high = np.nextafter(x_c, -inf)
+        elif ever_found_backet:
+            break
+        else:
+            x_high = x_c
+
+    # golden ratio algorithm
+        delta = x_high - x_low
+        while delta > 1e-12:
+            delta /= PHI
+            x_cc = x_high - delta
+
+            hl_1_1 = np.arctan(hl_1_1_in + x_3 * x_cc + x_4)
+            hl_1_2 = np.arctan(hl_1_2_in + x_8 * x_cc + x_9)
+            f_cc: float = hl_1_1 * x_10 + hl_1_2 * x_11
+            if f_cc < f_best:
+                x_best = x_cc
+                f_best = f_cc
+
+            x_dd = x_low + delta
+            hl_1_1 = np.arctan(hl_1_1_in + x_3 * x_dd + x_4)
+            hl_1_2 = np.arctan(hl_1_2_in + x_8 * x_dd + x_9)
+            f_dd: float = hl_1_1 * x_10 + hl_1_2 * x_11
+            if f_dd < f_best:
+                x_best = x_dd
+                f_best = f_dd
+
+            if f_cc < f_dd:
+                x_high = np.nextafter(x_dd, -inf)
+            else:
+                x_low = np.nextafter(x_cc, inf)
+            delta = x_high - x_low
+
+        # move forward to next bracket
+        x_a = x_b
+        f_a = f_b
+        x_b = x_c
+        f_b = f_c
+
+    out[0] = x_best
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __min_ann_2d_1o_2(state: np.ndarray, _: float,
+                      params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Minimize a poor man's ANN for 2d-spaces with one layer containing 2 nodes.
+
+    We use :func:`numpy.arctan` as activation function. The output layer
+    is just a weighted sum of the hidden layer and no other transformation
+    is performed. The ANN has one additional parameter besides the `state`.
+    We then find the value of this parameter for which the ANN takes on the
+    smallest value via an iterated bracket - golden ratio search.
+
+    :param state: the current state of the system
+    :param params: the weight vector for the neurons.
+    :param out: the control vector, receiving one single element
+    """
+    hl_1_1_in: Final[float] = (state * params[0:2]).sum()
+    x_2: Final[float] = params[2]
+    x_3: Final[float] = params[3]
+    hl_1_2_in: Final[float] = (state * params[4:6]).sum()
+    x_6: Final[float] = params[6]
+    x_7: Final[float] = params[7]
+    x_8: Final[float] = params[8]
+    x_9: Final[float] = params[9]
+
+# first estimate of the best value
+    x_best: float = 0.0
+    hl_1_1: float = np.arctan(hl_1_1_in + x_2 * x_best + x_3)
+    hl_1_2: float = np.arctan(hl_1_2_in + x_6 * x_best + x_7)
+    f_best: float = hl_1_1 * x_8 + hl_1_2 * x_9
+
+# now do bracketing
+    x_low = x_a = -1000.0
+    hl_1_1 = np.arctan(hl_1_1_in + x_2 * x_a + x_3)
+    hl_1_2 = np.arctan(hl_1_2_in + x_6 * x_a + x_7)
+    f_a: float = hl_1_1 * x_8 + hl_1_2 * x_9
+    if f_a < f_best:
+        x_best = x_a
+        f_best = f_a
+
+    x_c = x_b = -990.0
+    hl_1_1 = np.arctan(hl_1_1_in + x_2 * x_b + x_3)
+    hl_1_2 = np.arctan(hl_1_2_in + x_6 * x_b + x_7)
+    f_c = f_b = hl_1_1 * x_8 + hl_1_2 * x_9
+    if f_b < f_best:
+        x_best = x_b
+        f_best = f_b
+
+# we try optimization in every single bracket we find
+    found_bracket: bool = True
+    ever_found_backet: bool = False
+    while found_bracket:
+
+        found_bracket = False
+        while x_b < 1000.0:
+            x_c = x_b + 10.0
+            hl_1_1 = np.arctan(hl_1_1_in + x_2 * x_c + x_3)
+            hl_1_2 = np.arctan(hl_1_2_in + x_6 * x_c + x_7)
+            f_c = hl_1_1 * x_8 + hl_1_2 * x_9
+            if f_c < f_best:
+                x_best = x_c
+                f_best = f_c
+            if (f_c > f_b) and (f_b < f_a):
+                ever_found_backet = found_bracket = True
+                break
+            x_a = x_b
+            f_a = f_b
+            x_b = x_c
+            f_b = f_c
+
+    # use huge range -1e3 ... 1e3
+        if found_bracket:
+            x_low = np.nextafter(x_a, inf)
+            x_high = np.nextafter(x_c, -inf)
+        elif ever_found_backet:
+            break
+        else:
+            x_high = x_c
+
+    # golden ratio algorithm
+        delta = x_high - x_low
+        while delta > 1e-12:
+            delta /= PHI
+            x_cc = x_high - delta
+
+            hl_1_1 = np.arctan(hl_1_1_in + x_2 * x_cc + x_3)
+            hl_1_2 = np.arctan(hl_1_2_in + x_6 * x_cc + x_7)
+            f_cc: float = hl_1_1 * x_8 + hl_1_2 * x_9
+            if f_cc < f_best:
+                x_best = x_cc
+                f_best = f_cc
+
+            x_dd = x_low + delta
+            hl_1_1 = np.arctan(hl_1_1_in + x_2 * x_dd + x_3)
+            hl_1_2 = np.arctan(hl_1_2_in + x_6 * x_dd + x_7)
+            f_dd: float = hl_1_1 * x_8 + hl_1_2 * x_9
+            if f_dd < f_best:
+                x_best = x_dd
+                f_best = f_dd
+
+            if f_cc < f_dd:
+                x_high = np.nextafter(x_dd, -inf)
+            else:
+                x_low = np.nextafter(x_cc, inf)
+            delta = x_high - x_low
+
+        # move forward to next bracket
+        x_a = x_b
+        f_a = f_b
+        x_b = x_c
+        f_b = f_c
+
+    out[0] = x_best
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __min_ann_3d_1o_3(state: np.ndarray, _: float,
+                      params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Minimize a poor man's ANN for 3d-spaces with one layer containing 3 nodes.
+
+    We use :func:`numpy.arctan` as activation function. The output layer
+    is just a weighted sum of the hidden layer and no other transformation
+    is performed. The ANN has one additional parameter besides the `state`.
+    We then find the value of this parameter for which the ANN takes on the
+    smallest value via an iterated bracket - golden ratio search.
+
+    :param state: the current state of the system
+    :param params: the weight vector for the neurons.
+    :param out: the control vector, receiving one single element
+    """
+    hl_1_1_in: Final[float] = (state * params[0:3]).sum()
+    x_3: Final[float] = params[3]
+    x_4: Final[float] = params[4]
+    hl_1_2_in: Final[float] = (state * params[5:8]).sum()
+    x_8: Final[float] = params[8]
+    x_9: Final[float] = params[9]
+    hl_1_3_in: Final[float] = (state * params[10:13]).sum()
+    x_13: Final[float] = params[13]
+    x_14: Final[float] = params[14]
+    x_15: Final[float] = params[15]
+    x_16: Final[float] = params[16]
+    x_17: Final[float] = params[17]
+
+# first estimate of the best value
+    x_best: float = 0.0
+    hl_1_1: float = np.arctan(hl_1_1_in + x_3 * x_best + x_4)
+    hl_1_2: float = np.arctan(hl_1_2_in + x_8 * x_best + x_9)
+    hl_1_3: float = np.arctan(hl_1_3_in + x_13 * x_best + x_14)
+    f_best: float = hl_1_1 * x_15 + hl_1_2 * x_16 + hl_1_3 * x_17
+
+# now do bracketing
+    x_low = x_a = -1000.0
+    hl_1_1 = np.arctan(hl_1_1_in + x_3 * x_a + x_4)
+    hl_1_2 = np.arctan(hl_1_2_in + x_8 * x_a + x_9)
+    hl_1_3 = np.arctan(hl_1_3_in + x_13 * x_a + x_14)
+    f_a = hl_1_1 * x_15 + hl_1_2 * x_16 + hl_1_3 * x_17
+    if f_a < f_best:
+        x_best = x_a
+        f_best = f_a
+
+    x_c = x_b = -990.0
+    hl_1_1 = np.arctan(hl_1_1_in + x_3 * x_b + x_4)
+    hl_1_2 = np.arctan(hl_1_2_in + x_8 * x_b + x_9)
+    hl_1_3 = np.arctan(hl_1_3_in + x_13 * x_b + x_14)
+    f_c = f_b = hl_1_1 * x_15 + hl_1_2 * x_16 + hl_1_3 * x_17
+    if f_b < f_best:
+        x_best = x_b
+        f_best = f_b
+
+# we try optimization in every single bracket we find
+    found_bracket: bool = True
+    ever_found_backet: bool = False
+    while found_bracket:
+
+        found_bracket = False
+        while x_b < 1000.0:
+            x_c = x_b + 10.0
+            hl_1_1 = np.arctan(hl_1_1_in + x_3 * x_c + x_4)
+            hl_1_2 = np.arctan(hl_1_2_in + x_8 * x_c + x_9)
+            hl_1_3 = np.arctan(hl_1_3_in + x_13 * x_c + x_14)
+            f_c = hl_1_1 * x_15 + hl_1_2 * x_16 + hl_1_3 * x_17
+            if f_c < f_best:
+                x_best = x_c
+                f_best = f_c
+            if (f_c > f_b) and (f_b < f_a):
+                ever_found_backet = found_bracket = True
+                break
+            x_a = x_b
+            f_a = f_b
+            x_b = x_c
+            f_b = f_c
+
+    # use huge range -1e3 ... 1e3
+        if found_bracket:
+            x_low = np.nextafter(x_a, inf)
+            x_high = np.nextafter(x_c, -inf)
+        elif ever_found_backet:
+            break
+        else:
+            x_high = x_c
+
+    # golden ratio algorithm
+        delta = x_high - x_low
+        while delta > 1e-12:
+            delta /= PHI
+            x_cc = x_high - delta
+            hl_1_1 = np.arctan(hl_1_1_in + x_3 * x_cc + x_4)
+            hl_1_2 = np.arctan(hl_1_2_in + x_8 * x_cc + x_9)
+            hl_1_3 = np.arctan(hl_1_3_in + x_13 * x_cc + x_14)
+            f_cc = hl_1_1 * x_15 + hl_1_2 * x_16 + hl_1_3 * x_17
+            if f_cc < f_best:
+                x_best = x_cc
+                f_best = f_cc
+
+            x_dd = x_low + delta
+            hl_1_1 = np.arctan(hl_1_1_in + x_3 * x_dd + x_4)
+            hl_1_2 = np.arctan(hl_1_2_in + x_8 * x_dd + x_9)
+            hl_1_3 = np.arctan(hl_1_3_in + x_13 * x_dd + x_14)
+            f_dd = hl_1_1 * x_15 + hl_1_2 * x_16 + hl_1_3 * x_17
+            if f_dd < f_best:
+                x_best = x_dd
+                f_best = f_dd
+
+            if f_cc < f_dd:
+                x_high = np.nextafter(x_dd, -inf)
+            else:
+                x_low = np.nextafter(x_cc, inf)
+            delta = x_high - x_low
+
+        # move forward to next bracket
+        x_a = x_b
+        f_a = f_b
+        x_b = x_c
+        f_b = f_c
+
+    out[0] = x_best
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __min_ann_2d_1o_3(state: np.ndarray, _: float,
+                      params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Minimize a poor man's ANN for 2d-spaces with one layer containing 3 nodes.
+
+    We use :func:`numpy.arctan` as activation function. The output layer
+    is just a weighted sum of the hidden layer and no other transformation
+    is performed. The ANN has one additional parameter besides the `state`.
+    We then find the value of this parameter for which the ANN takes on the
+    smallest value via an iterated bracket - golden ratio search.
+
+    :param state: the current state of the system
+    :param params: the weight vector for the neurons.
+    :param out: the control vector, receiving one single element
+    """
+    hl_1_1_in: Final[float] = (state * params[0:2]).sum()
+    x_2: Final[float] = params[2]
+    x_3: Final[float] = params[3]
+    hl_1_2_in: Final[float] = (state * params[4:6]).sum()
+    x_6: Final[float] = params[6]
+    x_7: Final[float] = params[7]
+    hl_1_3_in: Final[float] = (state * params[8:10]).sum()
+    x_10: Final[float] = params[10]
+    x_11: Final[float] = params[11]
+    x_12: Final[float] = params[12]
+    x_13: Final[float] = params[13]
+    x_14: Final[float] = params[14]
+
+# first estimate of the best value
+    x_best: float = 0.0
+    hl_1_1: float = np.arctan(hl_1_1_in + x_2 * x_best + x_3)
+    hl_1_2: float = np.arctan(hl_1_2_in + x_6 * x_best + x_7)
+    hl_1_3: float = np.arctan(hl_1_3_in + x_10 * x_best + x_11)
+    f_best: float = hl_1_1 * x_12 + hl_1_2 * x_13 + hl_1_3 * x_14
+
+# now do bracketing
+    x_low = x_a = -1000.0
+    hl_1_1 = np.arctan(hl_1_1_in + x_2 * x_a + x_3)
+    hl_1_2 = np.arctan(hl_1_2_in + x_6 * x_a + x_7)
+    hl_1_3 = np.arctan(hl_1_3_in + x_10 * x_a + x_11)
+    f_a: float = hl_1_1 * x_12 + hl_1_2 * x_13 + hl_1_3 * x_14
+    if f_a < f_best:
+        x_best = x_a
+        f_best = f_a
+
+    x_c = x_b = -990.0
+
+    hl_1_1 = np.arctan(hl_1_1_in + x_2 * x_b + x_3)
+    hl_1_2 = np.arctan(hl_1_2_in + x_6 * x_b + x_7)
+    hl_1_3 = np.arctan(hl_1_3_in + x_10 * x_b + x_11)
+    f_c = f_b = hl_1_1 * x_12 + hl_1_2 * x_13 + hl_1_3 * x_14
+    if f_b < f_best:
+        x_best = x_b
+        f_best = f_b
+
+# we try optimization in every single bracket we find
+    found_bracket: bool = True
+    ever_found_backet: bool = False
+    while found_bracket:
+
+        found_bracket = False
+        while x_b < 1000.0:
+            x_c = x_b + 10.0
+            hl_1_1 = np.arctan(hl_1_1_in + x_2 * x_c + x_3)
+            hl_1_2 = np.arctan(hl_1_2_in + x_6 * x_c + x_7)
+            hl_1_3 = np.arctan(hl_1_3_in + x_10 * x_c + x_11)
+            f_c = hl_1_1 * x_12 + hl_1_2 * x_13 + hl_1_3 * x_14
+            if f_c < f_best:
+                x_best = x_c
+                f_best = f_c
+            if (f_c > f_b) and (f_b < f_a):
+                ever_found_backet = found_bracket = True
+                break
+            x_a = x_b
+            f_a = f_b
+            x_b = x_c
+            f_b = f_c
+
+    # use huge range -1e3 ... 1e3
+        if found_bracket:
+            x_low = np.nextafter(x_a, inf)
+            x_high = np.nextafter(x_c, -inf)
+        elif ever_found_backet:
+            break
+        else:
+            x_high = x_c
+
+    # golden ratio algorithm
+        delta = x_high - x_low
+        while delta > 1e-12:
+            delta /= PHI
+            x_cc = x_high - delta
+            hl_1_1 = np.arctan(hl_1_1_in + x_2 * x_cc + x_3)
+            hl_1_2 = np.arctan(hl_1_2_in + x_6 * x_cc + x_7)
+            hl_1_3 = np.arctan(hl_1_3_in + x_10 * x_cc + x_11)
+            f_cc: float = hl_1_1 * x_12 + hl_1_2 * x_13 + hl_1_3 * x_14
+            if f_cc < f_best:
+                x_best = x_cc
+                f_best = f_cc
+
+            x_dd = x_low + delta
+            hl_1_1 = np.arctan(hl_1_1_in + x_2 * x_dd + x_3)
+            hl_1_2 = np.arctan(hl_1_2_in + x_6 * x_dd + x_7)
+            hl_1_3 = np.arctan(hl_1_3_in + x_10 * x_dd + x_11)
+            f_dd: float = hl_1_1 * x_12 + hl_1_2 * x_13 + hl_1_3 * x_14
+            if f_dd < f_best:
+                x_best = x_dd
+                f_best = f_dd
+
+            if f_cc < f_dd:
+                x_high = np.nextafter(x_dd, -inf)
+            else:
+                x_low = np.nextafter(x_cc, inf)
+            delta = x_high - x_low
+
+        # move forward to next bracket
+        x_a = x_b
+        f_a = f_b
+        x_b = x_c
+        f_b = f_c
+
+    out[0] = x_best
+
+
+
+[docs] +def min_anns(system: System) -> Iterable[Controller]: + """ + Create poor man's ANNs for modeling the objective function. + + Based on the dimensionality of the state space, we generate a set of ANNs + with different numbers of layers and neurons. The weights of the neurons + can then directly be optimized by a numerical optimization algorithm. + This is, of course, probably much less efficient that doing some proper + learning like back-propagation. However, it allows us to easily plug the + ANNs into the same optimization routines as other controllers. + + :param system: the equations object + :return: the ANNs + """ + if system.control_dims != 1: + raise ValueError("invalid controller dimensions " + f"{system.control_dims} for {system!r}.") + if system.state_dims == 2: + return (Controller("min_ann_1", 2, 1, 3, __min_ann_2d_1o_1), + Controller("min_ann_2", 2, 1, 10, __min_ann_2d_1o_2), + Controller("min_ann_3", 2, 1, 15, __min_ann_2d_1o_3)) + if system.state_dims == 3: + return (Controller("min_ann_1", 3, 1, 4, __min_ann_3d_1o_1), + Controller("min_ann_2", 3, 1, 12, __min_ann_3d_1o_2), + Controller("min_ann_3", 3, 1, 18, __min_ann_3d_1o_3)) + raise ValueError("invalid state dimensions " + f"{system.state_dims} for {system!r}.")
+ +
diff --git a/_modules/moptipyapps/dynamic_control/controllers/partially_linear.html b/_modules/moptipyapps/dynamic_control/controllers/partially_linear.html new file mode 100644 index 00000000..1c7ccdfb --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/controllers/partially_linear.html @@ -0,0 +1,217 @@ +moptipyapps.dynamic_control.controllers.partially_linear — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.controllers.partially_linear

+"""
+Partially linear controllers.
+
+Partially linear controllers are encoded as sets of linear controllers and
+anchor points. The anchors are coordinates in the state space. For each state,
+the linear controller with the closest anchor point is used. In other words,
+these controllers are basically choices among multiple
+:mod:`~moptipyapps.dynamic_control.controllers.linear` controllers.
+"""
+
+from typing import Final, Iterable
+
+import numba  # type: ignore
+import numpy as np
+
+from moptipyapps.dynamic_control.controller import Controller
+from moptipyapps.dynamic_control.system import System
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __linear_2d_1o_2(state: np.ndarray, _: float,
+                     params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a double linear function for 2d state spaces.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+
+    d: float = ((s0 - params[0]) ** 2.0) + ((s1 - params[1]) ** 2.0)
+    o: float = (s0 * params[2]) + (s1 * params[3])
+
+    d2: float = ((s0 - params[4]) ** 2.0) + ((s1 - params[5]) ** 2.0)
+    if d2 < d:
+        o = (s0 * params[6]) + (s1 * params[7])
+    out[0] = o
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __linear_3d_1o_2(state: np.ndarray, _: float,
+                     params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a double linear function for 3d state spaces.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+    s2: Final[float] = state[2]
+
+    d: float = (((s0 - params[0]) ** 2.0) + ((s1 - params[1]) ** 2.0)
+                + ((s2 - params[2]) ** 2.0))
+    o: float = (s0 * params[3]) + (s1 * params[4]) + (s2 * params[5])
+
+    d2: float = (((s0 - params[6]) ** 2.0) + ((s1 - params[7]) ** 2.0)
+                 + ((s2 - params[8]) ** 2.0))
+    if d2 < d:
+        o = (s0 * params[9]) + (s1 * params[10]) + (s2 * params[11])
+
+    out[0] = o
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __linear_2d_1o_3(state: np.ndarray, _: float,
+                     params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a triple linear function for 2d state spaces.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+
+    d: float = ((s0 - params[0]) ** 2.0) + ((s1 - params[1]) ** 2.0)
+    o: float = (s0 * params[2]) + (s1 * params[3])
+
+    d2: float = ((s0 - params[4]) ** 2.0) + ((s1 - params[5]) ** 2.0)
+    if d2 < d:
+        o = (s0 * params[6]) + (s1 * params[7])
+
+    d2 = ((s0 - params[8]) ** 2.0) + ((s1 - params[9]) ** 2.0)
+    if d2 < d:
+        o = (s0 * params[10]) + (s1 * params[11])
+
+    out[0] = o
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __linear_3d_1o_3(state: np.ndarray, _: float,
+                     params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a double linear function for 3d state spaces.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+    s2: Final[float] = state[2]
+
+    d: float = (((s0 - params[0]) ** 2.0) + ((s1 - params[1]) ** 2.0)
+                + ((s2 - params[2]) ** 2.0))
+    o: float = (s0 * params[3]) + (s1 * params[4]) + (s2 * params[5])
+
+    d2: float = (((s0 - params[6]) ** 2.0) + ((s1 - params[7]) ** 2.0)
+                 + ((s2 - params[8]) ** 2.0))
+    if d2 < d:
+        o = (s0 * params[9]) + (s1 * params[10]) + (s2 * params[11])
+
+    d2 = (((s0 - params[12]) ** 2.0) + ((s1 - params[13]) ** 2.0)
+          + ((s2 - params[14]) ** 2.0))
+    if d2 < d:
+        o = (s0 * params[15]) + (s1 * params[16]) + (s2 * params[17])
+
+    out[0] = o
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __linear_2d_1o_4(state: np.ndarray, _: float,
+                     params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a quadruple linear function for 2d state spaces.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+
+    d: float = ((s0 - params[0]) ** 2.0) + ((s1 - params[1]) ** 2.0)
+    o: float = (s0 * params[2]) + (s1 * params[3])
+
+    d2: float = ((s0 - params[4]) ** 2.0) + ((s1 - params[5]) ** 2.0)
+    if d2 < d:
+        o = (s0 * params[6]) + (s1 * params[7])
+
+    d2 = ((s0 - params[8]) ** 2.0) + ((s1 - params[9]) ** 2.0)
+    if d2 < d:
+        o = (s0 * params[10]) + (s1 * params[11])
+
+    d2 = ((s0 - params[12]) ** 2.0) + ((s1 - params[13]) ** 2.0)
+    if d2 < d:
+        o = (s0 * params[14]) + (s1 * params[15])
+
+    out[0] = o
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __linear_3d_1o_4(state: np.ndarray, _: float,
+                     params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a quadruple linear function for 3d state spaces.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+    s2: Final[float] = state[2]
+
+    d: float = (((s0 - params[0]) ** 2.0) + ((s1 - params[1]) ** 2.0)
+                + ((s2 - params[2]) ** 2.0))
+    o: float = (s0 * params[3]) + (s1 * params[4]) + (s2 * params[5])
+
+    d2: float = (((s0 - params[6]) ** 2.0) + ((s1 - params[7]) ** 2.0)
+                 + ((s2 - params[8]) ** 2.0))
+    if d2 < d:
+        o = (s0 * params[9]) + (s1 * params[10]) + (s2 * params[11])
+
+    d2 = (((s0 - params[12]) ** 2.0) + ((s1 - params[13]) ** 2.0)
+          + ((s2 - params[14]) ** 2.0))
+    if d2 < d:
+        o = (s0 * params[15]) + (s1 * params[16]) + (s2 * params[17])
+
+    d2 = (((s0 - params[18]) ** 2.0) + ((s1 - params[19]) ** 2.0)
+          + ((s2 - params[20]) ** 2.0))
+    if d2 < d:
+        o = (s0 * params[21]) + (s1 * params[22]) + (s2 * params[23])
+
+    out[0] = o
+
+
+
+[docs] +def partially_linear(system: System) -> Iterable[Controller]: + """ + Create a several linear controllers for the given equations object. + + :param system: the equations object + :return: the partially linear controllers + """ + if system.control_dims != 1: + raise ValueError("invalid controller dimensions " + f"{system.control_dims} for {system!r}.") + if system.state_dims == 2: + return (Controller("linear_2", 2, 1, 8, __linear_2d_1o_2), + Controller("linear_3", 2, 1, 12, __linear_2d_1o_3), + Controller("linear_4", 2, 1, 16, __linear_2d_1o_4)) + if system.state_dims == 3: + return (Controller("linear_2", 3, 1, 12, __linear_3d_1o_2), + Controller("linear_3", 3, 1, 18, __linear_3d_1o_3), + Controller("linear_4", 3, 1, 24, __linear_3d_1o_4)) + raise ValueError("invalid state dimensions " + f"{system.state_dims} for {system!r}.")
+ +
diff --git a/_modules/moptipyapps/dynamic_control/controllers/peaks.html b/_modules/moptipyapps/dynamic_control/controllers/peaks.html new file mode 100644 index 00000000..ba778770 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/controllers/peaks.html @@ -0,0 +1,169 @@ +moptipyapps.dynamic_control.controllers.peaks — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.controllers.peaks

+"""
+Peak functions.
+
+Instead of synthesizing :mod:`~moptipyapps.dynamic_control.controllers.ann`
+with `arctan` as activation function, we here use `exp(-a²)` as activation
+function. This function does not represent a transition from -1 to 1, but a
+single peak around 0.
+"""
+
+from typing import Final, Iterable
+
+import numba  # type: ignore
+import numpy as np
+
+from moptipyapps.dynamic_control.controller import Controller
+from moptipyapps.dynamic_control.system import System
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __peak(a: float) -> float:
+    """
+    Compute the peak function.
+
+    :param a: the input
+    :return: the output
+    """
+    return np.exp(-(a * a))
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __peaks_2d_1o_1(state: np.ndarray, _: float,
+                    params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a single peak for 2d-spaces.
+
+    :param state: the current state of the system
+    :param params: the weight vector for the neurons.
+    :param out: the control vector, receiving one single element
+    """
+    out[0] = params[0] * __peak(params[1] + (params[2] * state[0])
+                                + (params[3] * state[1]))
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __peaks_3d_1o_1(state: np.ndarray, _: float,
+                    params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a single peak for 3d-spaces.
+
+    :param state: the current state of the system
+    :param params: the weight vector for the neurons.
+    :param out: the control vector, receiving one single element
+    """
+    out[0] = params[0] * __peak(
+        params[1] + (params[2] * state[0]) + (params[3] * state[1])
+        + (params[4] * state[2]))
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __peaks_2d_1o_2(state: np.ndarray, _: float,
+                    params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute two peaks for 2d-spaces.
+
+    :param state: the current state of the system
+    :param params: the weight vector for the neurons.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+    out[0] = params[0] * __peak(params[1] + (params[2] * s0)
+                                + (params[3] * s1)) \
+        + params[4] * __peak(params[5] + (params[6] * s0)
+                             + (params[7] * s1))
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __peaks_3d_1o_2(state: np.ndarray, _: float,
+                    params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a two peaks for 3d-spaces.
+
+    :param state: the current state of the system
+    :param params: the weight vector for the neurons.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+    s2: Final[float] = state[2]
+    out[0] = params[0] * __peak(
+        params[1] + (params[2] * s0) + (params[3] * s1) + (params[4] * s2))\
+        + params[5] * __peak(
+        params[6] + (params[7] * s0) + (params[8] * s1) + (params[9] * s2))
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __peaks_2d_1o_3(state: np.ndarray, _: float,
+                    params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute three peaks for 2d-spaces.
+
+    :param state: the current state of the system
+    :param params: the weight vector for the neurons.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+    out[0] = params[0] * __peak(params[1] + (params[2] * s0)
+                                + (params[3] * s1)) \
+        + params[4] * __peak(params[5] + (params[6] * s0)
+                             + (params[7] * s1)) \
+        + params[8] * __peak(params[9] + (params[10] * s0)
+                             + (params[11] * s1))
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __peaks_3d_1o_3(state: np.ndarray, _: float,
+                    params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a single peak for 3d-spaces.
+
+    :param state: the current state of the system
+    :param params: the weight vector for the neurons.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+    s2: Final[float] = state[2]
+    out[0] = params[0] * __peak(
+        params[1] + (params[2] * s0) + (params[3] * s1) + (params[4] * s2))\
+        + params[5] * __peak(
+        params[6] + (params[7] * s0) + (params[8] * s1) + (params[9] * s2))\
+        + params[10] * __peak(
+        params[11] + (params[12] * s0) + (params[13] * s1)
+        + (params[14] * s2))
+
+
+
+[docs] +def peaks(system: System) -> Iterable[Controller]: + """ + Create poor man's PNNs fitting to a given system. + + Based on the dimensionality of the state space, we generate a set of PNNs + with different numbers of layers and neurons. The weights of the neurons + can then directly be optimized by a numerical optimization algorithm. + This is, of course, probably much less efficient that doing some proper + learning like back-propagation. However, it allows us to easily plug the + PNNs into the same optimization routines as other controllers. + + :param system: the equations object + :return: the PNNs + """ + if system.control_dims != 1: + raise ValueError("invalid controller dimensions " + f"{system.control_dims} for {system!r}.") + if system.state_dims == 2: + return (Controller("peaks_1", 2, 1, 4, __peaks_2d_1o_1), + Controller("peaks_2", 2, 1, 8, __peaks_2d_1o_2), + Controller("peaks_3", 2, 1, 12, __peaks_2d_1o_3)) + if system.state_dims == 3: + return (Controller("peaks_1", 3, 1, 5, __peaks_3d_1o_1), + Controller("peaks_2", 3, 1, 10, __peaks_3d_1o_2), + Controller("peaks_3", 3, 1, 15, __peaks_3d_1o_3)) + raise ValueError("invalid state dimensions " + f"{system.state_dims} for {system!r}.")
+ +
diff --git a/_modules/moptipyapps/dynamic_control/controllers/predefined.html b/_modules/moptipyapps/dynamic_control/controllers/predefined.html new file mode 100644 index 00000000..070411bc --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/controllers/predefined.html @@ -0,0 +1,103 @@ +moptipyapps.dynamic_control.controllers.predefined — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.controllers.predefined

+"""
+A set of pre-defined controllers.
+
+In this module, we provide a set of pre-defined controllers taken from the
+works of NOACK, CORNEJO MACEDA, LI, and SUN of the Harbin Institute of
+Technology in Shenzhen, China (哈尔滨工业大学(深圳)). We ignore the
+parameterizations offered in the original works and instead synthesize the
+parameter values by ourselves.
+
+1. Yuxiang LI (李宇翔). Jet Mixing Enhancement using Deep Reinforcement
+   Learning (基于深度强化学习的射流混合增强控制). MSc Thesis. Harbin Institute
+   of Technology in Shenzhen, China (哈尔滨工业大学(深圳)).
+   January 2023.
+2. Wei SUN (孙伟). Wake Control of 1-2-3 Fluidic Pinball using Deep
+   Reinforcement Learning (基于深度强化学习方法的 1-2-3 流体弹球尾流控制).
+   MSc Thesis. Harbin Institute of Technology in Shenzhen, China
+   (哈尔滨工业大学(深圳)). January 2023.
+3. Guy Yoslan CORNEJO MACEDA, François LUSSEYRAN, and Bernd R. NOACK.
+   xMLC: A Toolkit for Machine Learning Control, First Edition.
+   Machine Learning Tools in Fluid Mechanics, Vol 2.
+   Shenzhen & Paris; Universitätsbibliothek der Technischen Universität
+   Braunschweig. 2022 https://doi.org/10.24355/dbbs.084-202208220937-0
+"""
+
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+
+from moptipyapps.dynamic_control.controller import Controller
+from moptipyapps.dynamic_control.system import System
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __cornejo_maceda(state: np.ndarray, _: float,
+                     params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a parameterized version of Cornejo Maceda's Law.
+
+    See page 16, chapter 3.3.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    z = np.tanh(state[0] - state[1])
+    b = params[0]
+    z = np.tanh(1.0 if b == 0 else z / b)
+    b = params[1]
+    z = np.tanh(1.0 if b == 0 else z / b)
+    b = params[2]
+    out[0] = np.tanh(1.0 if b == 0 else z / b)
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __table_3_1_ga(state: np.ndarray, _: float,
+                   params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute the law evolved by genetic algorithm in Table 3-1.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    out[0] = state[0] * params[0] + state[1] * params[1]
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __table_3_1_lgpc(state: np.ndarray, _: float,
+                     params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute the law evolved by LGPC algorithm in Table 3-1.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    a: Final[float] = state[0] * params[0] + params[1]
+    out[0] = params[2] * np.sin((params[3] / a) if (a != 0.0) else 1.0)
+
+
+
+[docs] +def predefined(system: System) -> tuple[Controller, ...]: + """ + Create a set of pre-defined controllers for the given equations object. + + :param system: the equations object + :return: the linear controller + """ + if system.control_dims != 1: + raise ValueError("invalid controller dimensions " + f"{system.control_dims} for {system!r}.") + if system.state_dims == 2: + return (Controller("cornejo_maceda", 2, 1, 3, __cornejo_maceda), ) + if system.state_dims == 3: + return (Controller("table_3_1_ga", 3, 1, 2, __table_3_1_ga), + Controller("table_3_1_lgpc", 3, 1, 4, __table_3_1_lgpc)) + raise ValueError("invalid state dimensions " + f"{system.state_dims} for {system!r}.")
+ +
diff --git a/_modules/moptipyapps/dynamic_control/controllers/quadratic.html b/_modules/moptipyapps/dynamic_control/controllers/quadratic.html new file mode 100644 index 00000000..a540b9a6 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/controllers/quadratic.html @@ -0,0 +1,76 @@ +moptipyapps.dynamic_control.controllers.quadratic — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.controllers.quadratic

+"""
+A quadratic controller.
+
+A quadratic controller is a function where all value of the state vector enter
+the computation plainly and squared. The powers of their combinations do not
+exceed two, e.g., the controller is a linear combination of A, B, A², AB, and
+B² if the state has values A and B. The controller represents the multipliers
+for these coefficients.
+"""
+
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+
+from moptipyapps.dynamic_control.controller import Controller
+from moptipyapps.dynamic_control.system import System
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __quadratic_2d_1o(state: np.ndarray, _: float,
+                      params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a quadratic function for 2d state spaces.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+    out[0] = (s0 * params[0]) + (s1 * params[1]) + (s0 * s0 * params[2]) \
+        + (s0 * s1 * params[3]) + (s1 * s1 * params[4])
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __quadratic_3d_1o(state: np.ndarray, _: float,
+                      params: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute a quadratic function for 3d state spaces.
+
+    :param state: the current state of the system
+    :param params: the parameters of the polynomial.
+    :param out: the control vector, receiving one single element
+    """
+    s0: Final[float] = state[0]
+    s1: Final[float] = state[1]
+    s2: Final[float] = state[2]
+    out[0] = (s0 * params[0]) + (s1 * params[1]) + (s2 * params[2]) \
+        + (s0 * s0 * params[3]) + (s0 * s1 * params[4]) \
+        + (s0 * s2 * params[5]) + (s1 * s1 * params[6]) \
+        + (s1 * s2 * params[7]) + (s2 * s2 * params[8])
+
+
+
+[docs] +def quadratic(system: System) -> Controller: + """ + Create a quadratic controller for the given equations object. + + :param system: the equations object + :return: the quadratic controller + """ + if system.control_dims != 1: + raise ValueError("invalid controller dimensions " + f"{system.control_dims} for {system!r}.") + name: Final[str] = "quadratic" + if system.state_dims == 2: + return Controller(name, 2, 1, 5, __quadratic_2d_1o) + if system.state_dims == 3: + return Controller(name, 3, 1, 9, __quadratic_3d_1o) + raise ValueError("invalid state dimensions " + f"{system.state_dims} for {system!r}.")
+ +
diff --git a/_modules/moptipyapps/dynamic_control/experiment_raw.html b/_modules/moptipyapps/dynamic_control/experiment_raw.html new file mode 100644 index 00000000..df1d399c --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/experiment_raw.html @@ -0,0 +1,153 @@ +moptipyapps.dynamic_control.experiment_raw — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.experiment_raw

+"""
+An example experiment for dynamic control.
+
+In this experiment, we try to synthesize (i.e., parameterize) controllers
+(:mod:`~moptipyapps.dynamic_control.controller`) that steer a dynamic system
+(:mod:`~moptipyapps.dynamic_control.system`) into a state by using a figure
+of merit (:mod:`~moptipyapps.dynamic_control.objective`) which minimizes both
+the squared system state and controller effort.
+
+A model-based experiment variant is given in
+:mod:`~moptipyapps.dynamic_control.experiment_surrogate`.
+"""
+
+import argparse
+from os.path import basename, dirname
+from typing import Any, Callable, Final, Iterable, cast
+
+import numpy as np
+from moptipy.algorithms.so.vector.cmaes_lib import BiPopCMAES
+from moptipy.api.execution import Execution
+from moptipy.api.experiment import run_experiment
+from moptipy.api.process import Process
+from pycommons.io.path import Path, directory_path
+
+from moptipyapps.dynamic_control.controllers.ann import anns
+from moptipyapps.dynamic_control.controllers.cubic import cubic
+from moptipyapps.dynamic_control.controllers.linear import linear
+from moptipyapps.dynamic_control.controllers.min_ann import min_anns
+from moptipyapps.dynamic_control.controllers.partially_linear import (
+    partially_linear,
+)
+from moptipyapps.dynamic_control.controllers.peaks import peaks
+from moptipyapps.dynamic_control.controllers.predefined import predefined
+from moptipyapps.dynamic_control.controllers.quadratic import quadratic
+from moptipyapps.dynamic_control.instance import Instance
+from moptipyapps.dynamic_control.objective import FigureOfMeritLE
+from moptipyapps.dynamic_control.systems.lorenz import LORENZ_111
+from moptipyapps.dynamic_control.systems.stuart_landau import STUART_LANDAU_111
+from moptipyapps.shared import moptipyapps_argparser
+
+
+
+[docs] +def make_instances() -> Iterable[Callable[[], Instance]]: + """ + Create the instances to be used in the dynamic control experiment. + + :return: the instances to be used in the dynamic control experiment. + """ + res: list[Callable[[], Instance]] = [] + for system in (STUART_LANDAU_111, LORENZ_111): + controllers = [linear(system), quadratic(system), cubic(system)] + controllers.extend(anns(system)) + controllers.extend(min_anns(system)) + controllers.extend(partially_linear(system)) + controllers.extend(predefined(system)) + controllers.extend(peaks(system)) + res.extend(cast( + Callable[[], Instance], lambda _s=system, _c=controller: + Instance(_s, _c)) for controller in controllers) + return res
+ + + +
+[docs] +def base_setup(instance: Instance) -> Execution: + """ + Create the basic setup. + + :param instance: the instance to use + :return: the basic execution + """ + return Execution().set_max_fes(512).set_log_all_fes(True)\ + .set_objective(FigureOfMeritLE(instance))
+ + + +
+[docs] +def cmaes(instance: Instance) -> Execution: + """ + Create the Bi-Pop-CMA-ES setup. + + :param instance: the problem instance + :return: the setup + """ + space = instance.controller.parameter_space() + return base_setup(instance).set_solution_space(space)\ + .set_algorithm(BiPopCMAES(space))
+ + + +
+[docs] +def on_completion(instance: Any, log_file: Path, process: Process) -> None: + """ + Plot the corresponding figures and print the objective value components. + + :param instance: the problem instance + :param log_file: the log file + :param process: the process + """ + inst: Final[Instance] = cast(Instance, instance) + dest_dir: Final[Path] = directory_path(dirname(log_file)) + base_name: str = basename(log_file) + base_name = base_name[:base_name.rindex(".")] + result: np.ndarray = cast(np.ndarray, process.create()) + process.get_copy_of_best_x(result) + j: Final[float] = process.get_best_f() + inst.describe_parameterization(f"F = {j}", result, base_name, dest_dir)
+ + + +
+[docs] +def run(base_dir: str, n_runs: int = 5) -> None: + """ + Run the experiment. + + :param base_dir: the base directory + :param n_runs: the number of runs + """ + use_dir: Final[Path] = Path(base_dir) + use_dir.ensure_dir_exists() + instances: Final[Iterable[Callable[[], Instance]]] = make_instances() + for maker in instances: + inst: Instance = maker() + inst.system.describe_system_without_control(use_dir, True) + inst.system.plot_points(use_dir, True) + + run_experiment( + base_dir=use_dir, + instances=instances, + setups=[cmaes], + n_runs=n_runs, + perform_warmup=False, + perform_pre_warmup=False, + on_completion=on_completion)
+ + + +# Run the experiment from the command line +if __name__ == "__main__": + parser: Final[argparse.ArgumentParser] = moptipyapps_argparser( + __file__, "Dynamic Control", "Run the dynamic control experiment.") + parser.add_argument( + "dest", help="the directory to store the experimental results under", + type=Path, nargs="?", default="./results/") + args: Final[argparse.Namespace] = parser.parse_args() + run(args.dest) +
diff --git a/_modules/moptipyapps/dynamic_control/experiment_surrogate.html b/_modules/moptipyapps/dynamic_control/experiment_surrogate.html new file mode 100644 index 00000000..84f50a88 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/experiment_surrogate.html @@ -0,0 +1,250 @@ +moptipyapps.dynamic_control.experiment_surrogate — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.experiment_surrogate

+"""
+An example experiment for dynamic control using surrogate system models.
+
+In this experiment, we again try to synthesize (i.e., parameterize)
+controllers (:mod:`~moptipyapps.dynamic_control.controller`) that steer a
+dynamic system (:mod:`~moptipyapps.dynamic_control.system`) into a state by
+using a figure of merit (:mod:`~moptipyapps.dynamic_control.objective`) which
+minimizes both the squared system state and controller effort.
+
+The difference compared to :mod:`~moptipyapps.dynamic_control.experiment_raw`
+is that we also try to synthesize a system model at the same time. We employ
+the procedure detailed in
+:mod:`~moptipyapps.dynamic_control.surrogate_optimizer` for this purpose.
+
+Word of advice: This experiment takes **extremely long** and needs
+**a lot of memory**!
+
+The starting points of the work here were conversations with Prof. Dr. Bernd
+NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute of Technology in
+Shenzhen, China (哈尔滨工业大学(深圳)).
+"""
+
+import argparse
+from os.path import basename, dirname
+from typing import Any, Callable, Final, Iterable, cast
+
+import numpy as np
+from moptipy.api.execution import Execution
+from moptipy.api.experiment import run_experiment
+from moptipy.api.process import Process
+from moptipy.spaces.vectorspace import VectorSpace
+from pycommons.io.path import Path, directory_path
+
+from moptipyapps.dynamic_control.controllers.ann import make_ann
+from moptipyapps.dynamic_control.instance import Instance
+from moptipyapps.dynamic_control.objective import (
+    FigureOfMerit,
+    FigureOfMeritLE,
+)
+from moptipyapps.dynamic_control.surrogate_optimizer import (
+    SurrogateOptimizer,
+    _bpcmaes,
+)
+from moptipyapps.dynamic_control.system_model import SystemModel
+from moptipyapps.dynamic_control.systems.lorenz import LORENZ_4
+from moptipyapps.dynamic_control.systems.stuart_landau import STUART_LANDAU_4
+from moptipyapps.dynamic_control.systems.three_coupled_oscillators import (
+    THREE_COUPLED_OSCILLATORS,
+)
+from moptipyapps.shared import moptipyapps_argparser
+
+
+
+[docs] +def make_instances() -> Iterable[Callable[[], SystemModel]]: + """ + Create the instances to be used in the dynamic control experiment. + + :return: the instances to be used in the dynamic control experiment. + """ + res: list[Callable[[], SystemModel]] = [] + for system in [STUART_LANDAU_4, LORENZ_4, THREE_COUPLED_OSCILLATORS]: + sd: int = system.state_dims + sdp2: int = sd + 2 + ctrl_dims: int = system.control_dims + controllers = [ + make_ann(sd, ctrl_dims, [sd, sd]), + make_ann(sd, ctrl_dims, [sdp2, sdp2])] + res.extend(cast( + Callable[[], SystemModel], + lambda _s=system, _c=controller, _m=make_ann( + sd + system.control_dims, sd, ann_model): SystemModel( + _s, _c, _m)) for ann_model in [[sd, sd, sd], [ + sd, sd, sd, sd], [sdp2, sdp2, sdp2]] + for controller in controllers) + return res
+ + + +#: the total objective function evaluations +MAX_FES: Final[int] = 64 + + +
+[docs] +def base_setup(instance: Instance) -> tuple[ + Execution, FigureOfMerit, VectorSpace]: + """ + Create the basic setup. + + :param instance: the instance to use + :return: the basic execution + """ + objective: Final[FigureOfMerit] = FigureOfMeritLE( + instance, isinstance(instance, SystemModel)) + space = instance.controller.parameter_space() + return Execution().set_max_fes(MAX_FES).set_log_all_fes(True)\ + .set_objective(objective).set_solution_space(space), objective, space
+ + + +
+[docs] +def cmaes_raw(instance: Instance) -> Execution: + """ + Create the Bi-Pop-CMA-ES setup. + + :param instance: the problem instance + :return: the setup + """ + execution, _, space = base_setup(instance) + return execution.set_algorithm(_bpcmaes(space))
+ + + +
+[docs] +def cmaes_surrogate(instance: SystemModel, + fes_for_warmup: int = 16, + fes_for_training: int = 128, + fes_per_model_run: int = 128, + fancy_logs: bool = True) -> Execution: + """ + Create the Bi-Pop-CMA-ES setup. + + :param instance: the problem instance + :param fes_for_warmup: the FEs to be used for warmup + :param fes_for_training: the milliseconds for training + :param fes_per_model_run: the milliseconds per model run + :param fancy_logs: should we do fancy logging? + :return: the setup + """ + execution, objective, space = base_setup(instance) + + return execution.set_solution_space(space).set_algorithm( + SurrogateOptimizer( + system_model=instance, + controller_space=space, + objective=objective, + fes_for_warmup=fes_for_warmup, + fes_for_training=fes_for_training, + fes_per_model_run=fes_per_model_run, + fancy_logs=fancy_logs, + warmup_algorithm=_bpcmaes, + model_training_algorithm=_bpcmaes, + controller_training_algorithm=_bpcmaes))
+ + + +
+[docs] +def on_completion(instance: Any, log_file: Path, process: Process) -> None: + """ + Plot the corresponding figures and print the objective value components. + + :param instance: the problem instance + :param log_file: the log file + :param process: the process + """ + inst: Final[SystemModel] = cast(SystemModel, instance) + dest_dir: Final[Path] = directory_path(dirname(log_file)) + base_name: str = basename(log_file) + base_name = base_name[:base_name.rindex(".")] + result: np.ndarray = cast(np.ndarray, process.create()) + process.get_copy_of_best_x(result) + j: Final[float] = process.get_best_f() + inst.describe_parameterization(f"F = {j}", result, base_name, dest_dir)
+ + + +
+[docs] +def run(base_dir: str, n_runs: int = 64) -> None: + """ + Run the experiment. + + :param base_dir: the base directory + :param n_runs: the number of runs + """ + use_dir: Final[Path] = Path(base_dir) + use_dir.ensure_dir_exists() + instances: Final[Iterable[Callable[[], SystemModel]]] = make_instances() + keep_instances: Final[list[Callable[[], Instance]]] = [] + raw_names: Final[set[str]] = set() + for maker in instances: + inst: Instance = maker() + raw: Instance = Instance(inst.system, inst.controller) + name: str = str(raw) + if name in raw_names: + continue + raw_names.add(name) + raw.system.describe_system_without_control(use_dir, True) + raw.system.plot_points(use_dir, True) + keep_instances.append(cast(Callable[[], Instance], lambda _i=raw: _i)) + + run_experiment( + base_dir=use_dir, + instances=keep_instances, + setups=[cmaes_raw], + n_runs=n_runs, + perform_warmup=False, + perform_pre_warmup=False, + on_completion=on_completion) + + setups: list[Callable[[Any], Execution]] = [] + total_training_fes: Final[int] = (MAX_FES - 1) * (2 ** 15) + total_on_model_fes: Final[int] = (MAX_FES - 1) * 2048 + + fe_choices: set[int] = {2 ** wfb for wfb in range(6)} + fe_choices.update(MAX_FES - (2 ** wfb) for wfb in range(6)) + + for warmup_fes in fe_choices: + if not (0 < warmup_fes < MAX_FES): + continue + training_fes: int = max(1, int(0.5 + (total_training_fes / ( + MAX_FES - warmup_fes)))) + on_model_fes: int = max(1, int(0.5 + (total_on_model_fes / ( + MAX_FES - warmup_fes)))) + + setups.append(cast( + Callable[[Any], Execution], + lambda i, __w=warmup_fes, __t=training_fes, __o=on_model_fes: + cmaes_surrogate(i, __w, __t, __o, True))) + + for runs in sorted({3, 5, 7, 11, 17, 23, 31, 51, n_runs}): + if runs > n_runs: + break + run_experiment( + base_dir=use_dir, + instances=instances, + setups=setups, + n_runs=runs, + perform_warmup=False, + perform_pre_warmup=False, + on_completion=on_completion)
+ + + +# Run the experiment from the command line +if __name__ == "__main__": + parser: Final[argparse.ArgumentParser] = moptipyapps_argparser( + __file__, "Dynamic Control", + "Run the dynamic control surrogate model experiment.") + parser.add_argument( + "dest", help="the directory to store the experimental results under", + type=Path, nargs="?", default="./results/") + args: Final[argparse.Namespace] = parser.parse_args() + run(args.dest) +
diff --git a/_modules/moptipyapps/dynamic_control/instance.html b/_modules/moptipyapps/dynamic_control/instance.html new file mode 100644 index 00000000..e057ab49 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/instance.html @@ -0,0 +1,133 @@ +moptipyapps.dynamic_control.instance — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.instance

+"""
+An instance of the dynamic control synthesis problem.
+
+An instance of the dynamic control synthesis problem is comprised of two
+components: a :mod:`~moptipyapps.dynamic_control.system` of differential
+equations governing how the state of a system changes over time and a
+:mod:`~moptipyapps.dynamic_control.controller` that uses the current system
+state as input and computes a controller value as output that influences the
+state change. Notice that the controller here is a parametric function. The
+goal of the dynamic system control is to find the right parameterization of
+the controller such that an :mod:`~moptipyapps.dynamic_control.objective` is
+minimized. The objective here usually has the goal to bring the dynamic system
+into a stable state while using as little controller "energy" as possible.
+
+An instance of the simultaneous control and model synthesis problem is an
+instance of the class :class:`~moptipyapps.dynamic_control.system_\
+model.SystemModel`, which is a subclass of :class:`~moptipyapps.\
+dynamic_control.instance.Instance`. It also adds a controller blueprint for
+modelling the systems response (state differential) based on the system state
+and controller output.
+
+The starting point of the work here were conversations with Prof. Dr. Bernd
+NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute of Technology in
+Shenzhen, China (哈尔滨工业大学(深圳)).
+"""
+
+from typing import Final
+
+import numpy as np
+from moptipy.api.component import Component
+from moptipy.utils.logger import KeyValueLogSection
+from moptipy.utils.strings import sanitize_name
+from pycommons.io.path import Path
+from pycommons.types import type_error
+
+from moptipyapps.dynamic_control.controller import Controller
+from moptipyapps.dynamic_control.system import System
+
+
+
+[docs] +class Instance(Component): + """An instance of the dynamic control problem.""" + + def __init__(self, system: System, controller: Controller, + name_base: str | None = None) -> None: + """ + Create an instance of the dynamic control problem. + + :param system: the system of equations governing the dynamic system + :param controller: the controller applied to the system + :param name_base: the name base + """ + super().__init__() + if not isinstance(system, System): + raise type_error(system, "system", System) + if not isinstance(controller, Controller): + raise type_error(controller, "controller", Controller) + if controller.state_dims != system.state_dims: + raise ValueError( + f"controller.state_dims={controller.state_dims}, but " + f"system.state_dims={system.state_dims} for controller " + f"{str(controller)!r} and system {str(system)!r}.") + if controller.control_dims != system.control_dims: + raise ValueError( + f"controller.control_dims={controller.control_dims}, but " + f"system.control_dims={system.control_dims} for controller " + f"{str(controller)!r} and system {str(system)!r}.") + #: the system governing the dynamic system + self.system: Final[System] = system + #: the controller applied to the system + self.controller: Final[Controller] = controller + + name: str = f"{self.system}_{self.controller}" + if name_base is not None: + if not isinstance(name_base, str): + raise type_error(name_base, "name_base", (str, None)) + nn = sanitize_name(name_base) + if nn != name_base: + raise ValueError(f"sanitized name base {nn!r} different " + f"from original {name_base!r}.") + name = f"{name}_{name_base}" + #: the name of this instance + self.name: Final[str] = name + + def __str__(self) -> str: + """ + Get the name of this instance. + + :return: a combination of the equation name and the controller name + """ + return self.name + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log all parameters of this component as key-value pairs. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + with logger.scope("sys") as scope: + self.system.log_parameters_to(scope) + with logger.scope("ctrl") as scope: + self.controller.log_parameters_to(scope)
+ + +
+[docs] + def describe_parameterization( + self, title: str | None, + parameters: np.ndarray, base_name: str, + dest_dir: str) -> tuple[Path, ...]: + """ + Describe the performance of a given system of system. + + :param title: the optional title + :param parameters: the controller parameters + :param base_name: the base name of the file to produce + :param dest_dir: the destination directory + :returns: the paths of the generated files + """ + the_title = self.controller.name + if title is not None: + the_title = f"{the_title}\n{title}" + return self.system.describe_system( + the_title, self.controller.controller, parameters, + base_name, dest_dir)
+
+ +
diff --git a/_modules/moptipyapps/dynamic_control/model_objective.html b/_modules/moptipyapps/dynamic_control/model_objective.html new file mode 100644 index 00000000..2dbc1ff1 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/model_objective.html @@ -0,0 +1,206 @@ +moptipyapps.dynamic_control.model_objective — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.model_objective

+"""
+An objective function that can be used to synthesize system models.
+
+We consider the dynamic system to be a function `D(s, c) = ds/dt`, where
+
+- `s` is the state vector (e.g., two-dimensional for the Stuart-Landau system,
+  see :mod:`~moptipyapps.dynamic_control.systems.stuart_landau`, and
+  three-dimensional for the Lorenz system, see
+  :mod:`~moptipyapps.dynamic_control.systems.lorenz`),
+- `c` is the control vector (one-dimensional in both cases), and
+- `ds/dt` is the state differential (again two respectively three-dimensional).
+
+The :class:`~moptipyapps.dynamic_control.objective.FigureOfMerit` objective
+function allows us to collect tuples of `(s,c)` and `ds/dt` vectors. So all we
+need to do is to train a model `M` that receives as input a vector `x=(s,c)`
+(where `(s,c)` be the concatenation of `s` and `c`) and fill as output a vector
+`ds/dt`.
+
+The objective function in this module minimizes the root mean square error
+over the model-computed `ds/dt` vectors and the actual `ds/dt` vectors.
+The model objective function is used by the
+:mod:`~moptipyapps.dynamic_control.surrogate_optimizer` algorithm.
+"""
+from typing import Callable, Final
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.api.objective import Objective
+from moptipy.utils.logger import KeyValueLogSection
+from moptipy.utils.nputils import DEFAULT_FLOAT
+
+from moptipyapps.dynamic_control.controller import Controller
+from moptipyapps.dynamic_control.objective import FigureOfMerit
+
+
+@numba.njit(cache=False, inline="always", fastmath=True, boundscheck=False)
+def _evaluate(x: np.ndarray, pin: np.ndarray, pout: np.ndarray,
+              temp_1: np.ndarray, temp_2: np.ndarray,
+              eq: Callable[[np.ndarray, float, np.ndarray,
+                            np.ndarray], None]) -> float:
+    """
+    Compute the RMSE differences between expected and actual model output.
+
+    :param x: the model parameterization
+    :param pin: the input vectors
+    :param pout: the expected output vectors, flattened
+    :param temp_1: the long temporary array to receive the result values
+    :param temp_2: the temporary array to receive the state output
+    :param eq: the equations
+    :return: the mean of the `log(x+1)` of the squared differences `x`
+
+    >>> vx = np.array([0.5, 0.2], float)
+    >>> vpin = np.array([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], float)
+    >>> vpout = np.array([[10, 5], [14, 9], [18, 8]], float)
+    >>> vtemp_1 = np.empty(3, float)
+    >>> vtemp_2 = np.empty(2, float)
+    >>> @numba.njit(cache=False, inline="always", fastmath=True)
+    ... def _func(lstate: np.ndarray, _: float, lx: np.ndarray,
+    ...           lout: np.ndarray) -> None:
+    ...     lout[:] = (lstate[0:2] * lx[0]) - lx[1] - lstate[-1]
+    >>> _evaluate(vx, vpin, vpout, vtemp_1, vtemp_2, _func)
+    22.680823177337874
+    >>> y_1_1 = ((0 * 0.5) - 0.2 - 3) - 10
+    >>> y_1_2 = ((1 * 0.5) - 0.2 - 3) - 5
+    >>> r_1 = y_1_1 ** 2 + y_1_2 ** 2
+    >>> y_2_1 = ((4 * 0.5) - 0.2 - 7) - 14
+    >>> y_2_2 = ((5 * 0.5) - 0.2 - 7) - 9
+    >>> r_2 = y_2_1 ** 2 + y_2_2 ** 2
+    >>> y_3_1 = ((8 * 0.5) - 0.2 - 11) - 18
+    >>> y_3_2 = ((9 * 0.5) - 0.2 - 11) - 8
+    >>> r_3 = y_3_1 ** 2 + y_3_2 ** 2
+    >>> np.sqrt(np.array([r_1, r_2, r_3])).mean()
+    22.680823177337874
+    """
+    for i, row in enumerate(pin):  # iterate over all row=(s, c) tuples
+        eq(row, 0.0, x, temp_2)  # store the equation results
+        temp_1[i] = np.square(np.subtract(temp_2, pout[i], temp_2),
+                              temp_2).sum()
+    return np.sqrt(temp_1, temp_1).mean()
+
+
+
+[docs] +class ModelObjective(Objective): + """ + The objective for modeling. + + This objective function works on sequences of tuples `(s, c)` of the + system state `s` and controller output `c` as well as the corresponding + `ds/dt` differentials. The goal is to train a model `M(s, c, q)` that will + compute the `ds/dt` values reasonably accurately. The model is + parameterized with some vector `q`, think of `M` being, e.g., an + artificial neural network and `q` being its weight vector. To find good + values of `q`, this objective here computes the squared differences + between the values `M(s, c, q)` and the expected outputs `ds/dt`. + + These squared differences are then either averaged directly or the + `expm1(mean(logp1(...))))` hack of the + :class:`~moptipyapps.dynamic_control.objective.FigureOfMeritLE` is used to + alleviate the impact of large differentials, depending on which "real" + (controller-synthesis) objective is passed into the constructor). + + The tuples `(s, c)` and `ds/dt` are pulled from the real objective with + the method :meth:`~moptipyapps.dynamic_control.model_objective.\ +ModelObjective.begin`. The `ds/dt` rows are flattened for performance reasons. + """ + + def __init__(self, real: FigureOfMerit, + model: Controller) -> None: + """ + Create a model objective compatible to the given figure of merit. + + :param real: the objective used for the real optimization problem + :param model: the model + """ + #: the equations of the model + self.__equations: Callable[[np.ndarray, float, np.ndarray, + np.ndarray], None] = model.controller + #: the result + self.__temp_1: np.ndarray | None = None + #: the input + self.__in: np.ndarray | None = None + #: the output + self.__out: np.ndarray | None = None + #: the differentials getter + self.__get_differentials: Final[Callable[[], tuple[ + np.ndarray, np.ndarray]]] = real.get_differentials + #: the temporary array + self.__temp_2: Final[np.ndarray] = np.empty( + real.instance.system.state_dims, DEFAULT_FLOAT) + #: the real figure of merit name + self.__real_name: Final[str] = str(real) + +
+[docs] + def begin(self) -> None: + """ + Begin a model optimization run. + + This function pulls the training data from the actual + controller-synthesis objective function, which is an instance of + :class:`~moptipyapps.dynamic_control.objective.FigureOfMerit`, via the + method :meth:`~moptipyapps.dynamic_control.objective.FigureOfMerit.\ +get_differentials` and allocates internal data structures accordingly. + """ + self.__in, self.__out = self.__get_differentials() + self.__temp_1 = np.empty(len(self.__out), self.__out.dtype)
+ + +
+[docs] + def evaluate(self, x: np.ndarray) -> float: + """ + Evaluate a model parameterization. + + :param x: the model parameterization + :return: the objective value + """ + return _evaluate(x, self.__in, self.__out, self.__temp_1, + self.__temp_2, self.__equations)
+ + +
+[docs] + def end(self) -> None: + """End a model optimization run and free the associated memory.""" + self.__temp_1 = None + self.__in = None + self.__out = None
+ + + def __str__(self) -> str: + """ + Get the name of this objective. + + :return: the name of this objective + """ + return "modelRMSE" + +
+[docs] + def lower_bound(self) -> float: + """ + Get the lower bound of the model objective, which is `0`. + + :returns: 0.0 + """ + return 0.0
+ + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log all parameters of this component as key-value pairs. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + logger.key_value("figureOfMeritName", self.__real_name) + logger.key_value("nSamples", + 0 if self.__in is None else len(self.__in))
+
+ +
diff --git a/_modules/moptipyapps/dynamic_control/objective.html b/_modules/moptipyapps/dynamic_control/objective.html new file mode 100644 index 00000000..fb8c17c5 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/objective.html @@ -0,0 +1,348 @@ +moptipyapps.dynamic_control.objective — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.objective

+"""
+An objective functions for the dynamic control problem.
+
+The dynamic control problem means that our system starts in a given state
+and we try to move it to a stable state by using control effort. Controllers
+are trained over several training states, for each of which we can compute a
+figure of merit.
+
+We offer two different approaches for this:
+
+- :class:`FigureOfMerit` computes the arithmetic mean `z` over the separate
+  figures of merit `J` of the training cases.
+- :class:`FigureOfMeritLE` tries to smooth out the impact of bad starting
+  states by computing `exp(mean[log(J + 1)]) - 1`.
+
+These objective functions also offer a way to collect the state+control and
+corresponding differential vectors.
+"""
+
+from math import expm1
+from typing import Callable, Final
+
+import numpy as np
+from moptipy.api.objective import Objective
+from moptipy.utils.logger import KeyValueLogSection
+from moptipy.utils.nputils import array_to_str
+from pycommons.types import type_error
+
+from moptipyapps.dynamic_control.instance import Instance
+from moptipyapps.dynamic_control.ode import diff_from_ode, j_from_ode, run_ode
+from moptipyapps.shared import SCOPE_INSTANCE
+
+
+
+[docs] +class FigureOfMerit(Objective): + """A base class for figures of merit.""" + + def __init__(self, instance: Instance, + supports_model_mode: bool = False) -> None: + """ + Create the figure-of-merit objective of the dynamic control problem. + + :param instance: the instance + :param supports_model_mode: `True` if this objective is supposed to + support alternating actual and model-based runs, `False` if it is + just applied to the actual instance (see :meth:`set_model` and + :meth:`get_differentials`). + """ + super().__init__() + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + #: the dynamic control instance + self.instance: Final[Instance] = instance + #: the simulation steps + self.__steps: Final[int] = instance.system.training_steps + #: the training time + self.__time: Final[float] = instance.system.training_time + #: the training starting states + self.__training: Final[np.ndarray] = \ + instance.system.training_starting_states + #: the results + self.__results: Final[np.ndarray] = np.empty( + len(self.__training), float) + #: the equations + self.__equations: Callable[[ + np.ndarray, float, np.ndarray, np.ndarray], None] = \ + instance.system.equations + #: the controller + self.__controller: Final[Callable[[ + np.ndarray, float, np.ndarray, np.ndarray], None]] = \ + instance.controller.controller + #: the controller dimension + self.__controller_dim: Final[int] = instance.controller.control_dims + #: the collection of state+control vectors + self.__collection_sc: Final[list[np.ndarray] | None] \ + = [] if supports_model_mode else None + #: the collection of differential vectors + self.__collection_df: Final[list[np.ndarray] | None] \ + = [] if supports_model_mode else None + #: should we collect data? + self.__collect: bool = self.__collection_df is not None + #: the state dimensions inside the `J` + self.__state_dims_in_j: Final[int] = instance.system.state_dims_in_j + #: the weight of the control effort + self.__gamma: Final[float] = instance.system.gamma + +
+[docs] + def initialize(self) -> None: + """Initialize the objective for use.""" + super().initialize() + if self.__collection_df is not None: + self.__collection_df.clear() + if self.__collection_sc is not None: + self.__collection_sc.clear() + self.set_raw()
+ + +
+[docs] + def set_raw(self) -> None: + """ + Let this objective work on the original system equations. + + The objective function here can be used in two modi: a) based on the + original systems model, as given in + :attr:`~moptipyapps.dynamic_control.instance.Instance.system`, or b) + on a learned model of the system. This function here toggles to the + former mode, i.e., to the actual system mode. In this modus, training + data for training the system model will be gathered if the objective + function is configured to do so. In that case, you can toggle to model + mode via :meth:`set_model`. + """ + self.__equations = self.instance.system.equations + self.__collect = self.__collection_sc is not None
+ + +
+[docs] + def get_differentials(self) -> tuple[np.ndarray, np.ndarray]: + """ + Get the collected differentials. + + If `supports_model_mode` was set to `True` in the creating of this + objective function, then the system will gather tuples `(s, c)` and + `ds/dt` when in raw mode (see :meth:`set_raw`) and make them available + here to train system models (see :meth:`set_model`). Notice that + gathering training data is a very memory intense process. + + :returns: the collected differentials + """ + clsc: Final[list[np.ndarray] | None] = self.__collection_sc + cldf: Final[list[np.ndarray] | None] = self.__collection_df + if clsc is None: + raise ValueError("Differential collection not supported.") + if len(clsc) == 1: + return clsc[0], cldf[0] # pylint: disable=E1136 + sc: Final[np.ndarray] = np.concatenate(clsc) # pylint: disable=E1133 + df: Final[np.ndarray] = np.concatenate(cldf) # pylint: disable=E1133 + clsc.clear() + clsc.append(sc) + cldf.clear() + cldf.append(df) + return sc, df
+ + +
+[docs] + def set_model(self, equations: Callable[[ + np.ndarray, float, np.ndarray, np.ndarray], None]) -> None: + """ + Set the model-driven mode for the evaluation. + + In this modus, the internal system equations are replaced by the + callable `equations` passed into this function and the data collection + is stopped. The idea is that `equations` could be a model synthesized + on the data gathered (see :meth:`get_differentials`) and thus does not + represent the actual dynamic system but a model thereof. We could + synthesize a controller for this model and for this purpose would use + the exactly same objective function -- just instead of using the + actual system equations, we use the system model. Of course, we then + need to deactivate the data gathering mechanism (see again + :meth:`get_differentials`), because the data would then not be real + system data. You can toggle back to the actual system using + :meth:`set_raw`. + + :param equations: the equations to be used instead of the actual + system's differential equations. + """ + if self.__collection_sc is None: + raise ValueError("Cannot go into model mode without gathering " + "model training data!") + self.__equations = equations + self.__collect = False
+ + + def __str__(self) -> str: + """ + Get the name of this objective function. + + :return: `figureOfMerit` + """ + return "figureOfMerit" + + def __append(self, data: tuple[np.ndarray, np.ndarray]) -> None: + """ + Internally collect the data. + + :param data: the data to collect + """ + self.__collection_sc.append(data[0]) + self.__collection_df.append(data[1]) + +
+[docs] + def evaluate(self, x: np.ndarray) -> float: + """ + Evaluate the parameterization of a controller. + + :param x: the controller parameters + :return: the figure of merit + """ + steps: Final[int] = self.__steps + time: Final[float] = self.__time + training: Final[np.ndarray] = self.__training + results: Final[np.ndarray] = self.__results + equations: Final[Callable[[ + np.ndarray, float, np.ndarray, np.ndarray], None]] \ + = self.__equations + controller: Final[ + Callable[[np.ndarray, float, np.ndarray, np.ndarray], None]] \ + = self.__controller + controller_dim: Final[int] = self.__controller_dim + collector: Final[Callable[[tuple[ + np.ndarray, np.ndarray]], None] | None] = \ + self.__append if self.__collect else None + state_dim: Final[int] = len(training[0]) + state_dims_in_j: Final[int] = self.__state_dims_in_j + gamma: Final[float] = self.__gamma + + for i, start in enumerate(training): + # The following line makes no sense at all. It creates a copy of + # the flattened version of the (already flat) start. The copy is + # stored nowhere, so it is immediately discarded. The value of + # start is not changed. However, the numpy array container + # changes, for an unclear reason. This is required and it must + # happen exactly here, for an unclear reason. Otherwise, the + # results of the objective function are inconsistent. For an + # unclear reason. + np.copy(start.flatten()) # <--- This should make no sense... + the_ode = run_ode( + start, equations, controller, x, controller_dim, steps, time) + results[i] = z = j_from_ode( + the_ode, state_dim, state_dims_in_j, gamma) + if not (0.0 <= z <= 1e100): + return 1e200 + if collector is not None: + collector(diff_from_ode(the_ode, state_dim)) + z = self.sum_up_results(results) + return z if 0.0 <= z <= 1e100 else 1e200
+ + +
+[docs] + def sum_up_results(self, results: np.ndarray) -> float: + """ + Compute the final objective value from several single `J` values. + + When synthesizing controllers, we do not just apply them to a single + simulation run. Instead, we use multiple training cases (see + :attr:`~moptipyapps.dynamic_control.system.System.\ +training_starting_states`) and perform :attr:`~moptipyapps.dynamic_control\ +.system.System.training_steps` simulation steps on each of them. Each such + training starting state will result in a single `J` value, which is + the sum of squared state and control values. We now compute the end + objective value from these different `J` values by using this + function here. + + This will *destroy* the contents of `results`. + + :param results: the array of `J` values + :return: the final result + """ + return float(results.mean())
+ + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log all parameters of this component as key-value pairs. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + logger.key_value("modelModeEnabled", self.__collection_sc is not None) + logger.key_value("dataCollecting", self.__collect) + eq: Final = self.__equations + logger.key_value("usingOriginalEquations", + eq is self.instance.system.equations) + mp: Final[str] = "modelParameters" + if hasattr(self.__equations, mp): + logger.key_value(mp, array_to_str(getattr(eq, mp))) + with logger.scope(SCOPE_INSTANCE) as scope: + self.instance.log_parameters_to(scope)
+ + +
+[docs] + def lower_bound(self) -> float: + """ + Get the lower bound of the figure of merit, which is 0. + + :returns: 0.0 + """ + return 0.0
+
+ + + +
+[docs] +class FigureOfMeritLE(FigureOfMerit): + """ + Compute a `exp(mean(log(z+1)))-1` over the figures of merit `z`. + + Different from :class:`FigureOfMerit`, we compute the mean of `log(z + 1)` + where `z` be the figures of merit of the single training cases. We then + return `exp(mean[log(z + 1)]) - 1` as final result. The goal is to reduce + the impact of training cases that require more control effort. + + If we solve the dynamic control problem for diverse training cases, then + we may have some very easy cases, where the system just needs a small + control impulse to move into a stable and cheap state. Others may have + very far out and expensive starting states that require lots of control + efforts to be corrected. If we simply average over all states, then these + expensive states will dominate whatever good we are doing in the cheap + states. Averaging over the `log(J+1)` reduces such impact. We then compute + `exp[...]-1` of the result as cosmetics to get back into the original + range of the figure of merits. + """ + + def __str__(self) -> str: + """ + Get the name of this objective function. + + :return: `figureOfMeritLE` + """ + return "figureOfMeritLE" + +
+[docs] + def sum_up_results(self, results: np.ndarray) -> float: + """ + Compute the final objective value from several single `J` values. + + For each training case, there is one basic figure of merit `J` and + here we compute `exp(mean[log(J + 1)]) - 1` over all of these values. + + :param results: the array of `J` values + :return: the final result + """ + return float(expm1(np.log1p(results, results).mean()))
+
+ +
diff --git a/_modules/moptipyapps/dynamic_control/ode.html b/_modules/moptipyapps/dynamic_control/ode.html new file mode 100644 index 00000000..d6302b3e --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/ode.html @@ -0,0 +1,647 @@ +moptipyapps.dynamic_control.ode — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.ode

+"""
+A primitive integrator for systems of ordinary differential equations.
+
+Many dynamic systems can be modeled as systems of ordinary differential
+equations that govern their progress over time. Trying to find out in
+which state such systems are at a given point in time means to integrate
+these equations until that point in time (starting from a starting state).
+
+What we want to play around with, however, is synthesizing controllers.
+In this case, the differential equations also merge the output of the
+controller with the current state. If the controller behaves inappropriately,
+this may make the system diverge, i.e., some of its state variables go to
+infinity over time or sometimes rather quickly.
+
+Using ODE integrators that compute the system state at pre-defined time steps
+is thus cumbersome, as the system may have already exploded at these goal
+times. Therefore, we perform ODE integration in several steps. First, we try
+it the "normal" way. However, as soon as the system escapes the sane parameter
+range, we stop. We then use the last point where the system was stable and the
+first point where it escaped the reasonable range to estimate a new reasonable
+end time for the integration. We do this until we finally succeed.
+
+Thus, we can simulate a well-behaved system over a long time and an
+ill-behaved system for a shorter time period. Neither system will diverge.
+
+The following functions are provided:
+
+- :func:`run_ode` executes a set of differential system equations and
+  controller equations and returns an array with the system state, controller
+  output, and time at the different interpolation steps.
+- :func:`t_from_ode` returns the total time over which the result of
+  :func:`run_ode` was simulated.
+- :func:`j_from_ode` returns a figure of merit, i.e., a weighted sum of (a
+  part of) the system state and the controller output from the result of
+  :func:`run_ode`.
+- :func:`diff_from_ode` extracts state difference information from the result
+  of :func:`run_ode`.
+"""
+
+from math import fsum, inf
+from typing import Callable, Final, Iterable, TypeVar
+
+import numba  # type: ignore
+import numpy as np
+from scipy.integrate import RK45, DenseOutput  # type: ignore
+
+#: the type variable for ODE controller parameters
+T = TypeVar("T")
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def _is_ok(x: np.ndarray) -> bool:
+    """
+    Check whether all values in a vector are acceptable.
+
+    A vector is "ok" if all of its elements are from the finite range
+    `(-1e10, 1e10)`.  Anything else indicates that we are somehow moving
+    out of the reasonable bounds.
+
+    :param x: the vector
+    :return: `True` if all values are OK, `False` otherwise
+    """
+    for xx in x:  # noqa: SIM110
+        if not -1e10 < xx < 1e10:
+            return False
+    return True
+
+
+class __IntegrationState:
+    """
+    The internal integrator state class.
+
+    This class serves two purposes. First, it encapsulates the system
+    equations and the controller equations such that they can be called as a
+    unit. Second, it raises an alert if the system escapes the "OK" state,
+    i.e., diverge towards infinity. As long as everything is OK,
+    :attr:`is_ok` will remain `True`. But if either the controller or the
+    system state escapes the acceptable interval, it becomes `False`.
+    In that case, :attr:`max_ok_t` holds the highest `t` value at which
+    the system and controller were in an acceptable state and
+    :attr:`min_error_t` holds the smallest `t` value at which that was not
+    the case. We can assume that somewhere inbetween lies the last moment we
+    can find the system in a sane state.
+    """
+
+    def __init__(self,
+                 equations: Callable[[
+                     np.ndarray, float, np.ndarray, np.ndarray], None],
+                 controller: Callable[[
+                     np.ndarray, float, T, np.ndarray], None],
+                 parameters: T, controller_dim: int) -> None:
+        """
+        Create the integrator.
+
+        :param equations: the differential system
+        :param controller: the controller function
+        :param parameters: the controller parameters
+        :param controller_dim: the dimension of the controller result
+        """
+        self.__equations: Final[Callable[[
+            np.ndarray, float, np.ndarray, np.ndarray], None]] = equations
+        self.__controller: Final[Callable[[
+            np.ndarray, float, T, np.ndarray], None]] = controller
+        self.__parameters: Final[T] = parameters
+        self.__ctrl: Final[np.ndarray] = np.empty(controller_dim)
+        self.max_ok_t: float = -inf
+        self.min_error_t: float = inf
+        self.is_ok: bool = True
+
+    def init(self) -> None:
+        """Prepare the system for integration."""
+        self.max_ok_t = -inf
+        self.min_error_t = inf
+        self.is_ok = True
+
+    def f(self, t: float, state: np.ndarray) -> np.ndarray:
+        """
+        Compute the differential at the given state and time.
+
+        First, we invoke the controller function at the state and time. Then
+        we pass the controller vector to the differential equations to update
+        the system state.
+        This function also checks if the state or control go out of bounds
+        and if they do, it sets the :attr:`is_ok` to `False`.
+
+        :param t: the time
+        :param state: the state
+        :return: the differential
+        """
+        out: Final[np.ndarray] = np.zeros_like(state)  # allocate output vec
+        ctrl: Final[np.ndarray] = self.__ctrl  # the controller vector
+
+        # invoke the controller
+        self.__controller(state, t, self.__parameters, ctrl)
+        ok: bool = _is_ok(ctrl)  # is the controller vector ok?
+        if ok:  # if yes, let's invoke the state update equations
+            self.__equations(state, t, ctrl, out)
+            ok = _is_ok(out)  # and check if their result is ok
+        if ok:  # is it ok?
+            if self.max_ok_t < t < self.min_error_t:
+                self.max_ok_t = t
+        else:  # no: there was some error, either in state or controller
+            self.is_ok = False  # then we are no longer OK
+            self.min_error_t = min(self.min_error_t, t)
+            if self.max_ok_t > t:  # what? an earlier error?
+                self.max_ok_t = np.nextafter(t, -inf)  # ok, reset ok time
+
+        return out
+
+
+
+[docs] +def run_ode(starting_state: np.ndarray, + equations: Callable[[ + np.ndarray, float, np.ndarray, np.ndarray], None], + controller: Callable[[np.ndarray, float, T, np.ndarray], None], + parameters: T, controller_dim: int = 1, + steps: int = 5000, max_time: float = 50.0) -> np.ndarray: + """ + Simulate a set of controlled differential system. + + The system begins in the starting state stored in the vector + `starting_state`. In each time step, first, the `controller` is invoked. + It receives the current system state vector as input, the current time + `t`, its parameters (`parameters`), and an output array to store its + computed control values into. This array has dimension `controller_dim`, + which usually is `1`. Then, the function `system` will be called and + receives the current state vector, the time step `t`, and the controller + output as input, as well as an output array to store the result of the + differential into. This output array has the same dimension as the state + vector. + + Now the `run_ode` function simulates such a system over `steps` time steps + over the closed interval `[0, max_time]`. If both the system and the + controller are well-behaved, then the output array will contain `steps` + rows with the state, controller, and time information of each step. If + the system diverges at some point in time but we can simulate it + reasonably well before that, then we try to simulate `steps`, but on a + shorter time frame. If even that fails, you will get a single row output + with `1e100` as the controller value. + + This function returns a matrix where each row corresponds to a simulated + time step. Each row contains three components in a concatenated fashion: + 1. the state vector, + 2. the control vector, + 3. the time value + + :param starting_state: the starting + :param equations: the differential system + :param controller: the controller function + :param parameters: the controller parameters + :param controller_dim: the dimension of the controller result + :param steps: the number of steps to simulate + :param max_time: the maximum time to simulate for + :returns: a matrix where each row represents a point in time, composed of + the current state, the controller output, and the length of the time + slice + + If we simulate the flight of a projectile with our ODE execution, then + both the flight time as well as the flight length are about 0.12% off from + what the mathematical solution of the flight system prescribe. That's + actually not bad for a crude and fast integration method... + + >>> v = 100.0 + >>> angle = np.deg2rad(45.0) + >>> v_x = v * np.cos(angle) + >>> print(f"{v_x:.10f}") + 70.7106781187 + >>> v_y = v * np.sin(angle) + >>> print(f"{v_y:.10f}") + 70.7106781187 + >>> def projectile(position, ttime, ctrl, out): + ... out[0] = 70.71067811865474 + ... out[1] = 70.71067811865474 - ttime * 9.80665 + >>> param = 0.0 # ignore + >>> def contrl(position, ttime, params, dest): + ... dest[0] = 0.0 # controller that does nothing + >>> strt = np.array([0.0, 1.0]) + >>> ode = run_ode(strt, projectile, contrl, param, 1, 10000) + >>> print(len(ode)) + 10000 + >>> time_of_flight = 2 * v_y / 9.80665 + >>> print(f"{time_of_flight:.10f}") + 14.4209649817 + >>> travel_distance_x = time_of_flight * v_x + >>> print(f"{travel_distance_x:.10f}") + 1019.7162129779 + >>> idx = np.argwhere(ode[:, 1] <= 0.0)[0][0] + >>> print(idx) + 2887 + >>> print(f"{ode[idx - 1, 0]:.10f}") + 1020.4571309653 + >>> print(f"{ode[idx, 0]:.10f}") + 1020.8107197148 + >>> print(f"{ode[idx - 1, -1]:.10f}") + 14.4314431443 + >>> print(f"{ode[idx, -1]:.10f}") + 14.4364436444 + >>> print(ode[-1, -1]) + 50.0 + + >>> def contrl2(position, ttime, params, dest): + ... dest[0] = 1e50 # controller that is ill-behaved + >>> run_ode(strt, projectile, contrl2, param, 1, 10000) + array([[0.e+000, 1.e+000, 1.e+100, 0.e+000]]) + + >>> def contrl3(position, ttime, params, dest): + ... dest[0] = 1e50 if ttime > 10 else 0.0 # diverging controller + >>> ode = run_ode(strt, projectile, contrl3, param, 1, 10000) + >>> print(len(ode)) + 10000 + >>> print(ode[-1]) + [690.10677249 224.06765771 0. 9.75958357] + + >>> def projectile2(position, ttime, ctrl, out): + ... out[:] = 0 + >>> ode = run_ode(strt, projectile2, contrl, param, 1, 10000) + >>> print(len(ode)) + 10000 + >>> print(ode[-1]) + [ 0. 1. 0. 50.] + >>> ode = run_ode(strt, projectile2, contrl3, param, 1, 10000) + >>> print(len(ode)) + 10000 + >>> print(ode[-1]) + [0. 1. 0. 9.41234557] + """ + func_state: Final[__IntegrationState] = __IntegrationState( + equations, controller, parameters, controller_dim) + func_call: Final[Callable[[float, np.ndarray], np.ndarray]] = func_state.f + denses: Final[list[DenseOutput]] = [] + n: Final[int] = len(starting_state) + dim: Final[int] = n + controller_dim + 1 + + cycle: int = 0 + while True: # loop until we have a sane integration over a sane range + cycle += 1 + # first, we reset all the state information + func_state.init() # reset the function state + denses.clear() # always discard all interpolators, if there are any + + # then we create the integrator for the time range that we simulate + integration = RK45( + fun=func_call, t0=0.0, y0=starting_state, t_bound=max_time, + max_step=steps) + is_finished: bool = False + + # perform the integration and collect all the points at which stuff + # was computed + while True: # iteratively add interpolation points + integration.step() # do the integration step + if not func_state.is_ok: + break # some out-of-bounds thing happened! quit! + is_finished = integration.status == "finished" + is_running: bool = integration.status == "running" + if is_finished or is_running: # keep taking interpolator + denses.append(integration.dense_output()) + if is_finished: + break # we are finished, so we quit and build output + continue # more integration to do, so we go on + break # if we get here, there was an error: quit + + if is_finished and func_state.is_ok: + # if we get here, everything looks fine so far, so we can try + # to build the output + result: np.ndarray = np.zeros((steps, dim)) + point: np.ndarray = result[0] + point[0:n] = starting_state + + # we now compute all the points by using the interpolation + # we start with the first point + controller(starting_state, 0.0, parameters, point[n:-1]) + if not _is_ok(point): + break + result[:, -1] = np.linspace(0.0, max_time, steps) + + j: int = 0 # the index of the dense interpolator to use + n_dense: int = len(denses) # the number of interpolators + t: float = 0.0 # the current time + dense: DenseOutput = denses[j] # the current interpolator + for point in result[1:]: # for each of the remaining points... + last_time: float = t # remember the last successful point + t = point[-1] # get the time value + + # we now need to find the right interpolator if we have left + # the range of the current interpolator + while not (dense.t_min <= t <= dense.t_max): + j += 1 # step counter + if j >= n_dense: # we have left the interpolation range?? + func_state.max_ok_t = last_time # so we need to adjust + func_state.min_error_t = t + is_finished = False # and try the whole thing again + break + dense = denses[j] # pick next interpolator + + if is_finished: # if we get here, we got a right interpolator + point[0:n] = dense(t) # so we can interpolate the state + controller(point[0:n], t, parameters, point[n:-1]) + if not _is_ok(point): # is there an error in the vector? + func_state.max_ok_t = last_time # last ok time + func_state.min_error_t = t # error time + is_finished = False # we need to quit and try again + break # stop inner loop + + if is_finished: # did we succeed? + return result # yes! return the result. + + # if we arrive here, things went wrong somehow. + # this means that we should reduce the maximum runtime + if (cycle < 3) and (func_state.max_ok_t < func_state.min_error_t): + max_time = np.nextafter(min(func_state.min_error_t, ( + 0.8 * func_state.max_ok_t) + (0.2 * func_state.min_error_t)), + -inf) + else: # the small reductions did not work out well ... reduce rapidly + max_time = np.nextafter(0.7 * min(func_state.max_ok_t, + max_time), -inf) + + if (cycle > 4) or (max_time <= 1e-10): + break # if we get here, everything seems so pointless... + + # the default error result + result = np.zeros((1, dim)) + result[0, 0:n] = starting_state + result[0, n:-1] = 1e100 + result[0, -1] = 0.0 + return result
+ + + +@numba.njit(cache=True, inline="always", fastmath=False, boundscheck=False) +def __j_from_ode_compute(ode: np.ndarray, state_dim: int, + use_state_dims: int, + gamma: float, + dest: np.ndarray) -> None: + """ + Prepare the input array for the figure of merit computation. + + The `ode` matrix contains one row for each time step. + The row is composed of the current state, the current controller output, + and the current time. + However, all systems of a given simulation start in the same initial + state, so it makes little sense to include this initial state into the + figure of merit computation. It does make sense to include the control + output at that moment, though, because it contributes to the next state. + It also makes no sense to count in the last row of the ODE computation, + because this is the final system state and the system will not spend any + time in it in the simulation. + + :param ode: the output array from the ODE simulation + :param state_dim: the state dimension + :param use_state_dims: the dimension until which the state is used + :param gamma: the weight for the control variable + :param dest: the destination array + + >>> od = np.array([[1, 2, 3, 4, 0], + ... [5, 6, 7, 8, 1], + ... [9, 6, 4, 3, 3], + ... [7, 4, 2, 1, 7]]) + >>> sta_dim = 3 + >>> dst = np.empty((od.shape[0] - 1) * (od.shape[1] - 1) - sta_dim) + >>> __j_from_ode_compute(od, sta_dim, sta_dim, 0.1, dst) + >>> print(dst) + [ 1.6 12.8 98. 72. 50. 3.6 64. 144. 324. ] + >>> rs = np.array([0.1 * 1 * 4 * 4, + ... 0.1 * 2 * 8 * 8, 2 * 7 * 7, 2 * 6 * 6, 2 * 5 * 5, + ... 0.1 * 4 * 3 * 3, 4 * 4 * 4, 4 * 6 * 6, 4 * 9 * 9]) + >>> print(rs) + [ 1.6 12.8 98. 72. 50. 3.6 64. 144. 324. ] + >>> u_sta_dim = 2 + >>> dst = np.empty((od.shape[0] - 1) * ( + ... od.shape[1] - 1 - sta_dim + u_sta_dim) - u_sta_dim) + >>> __j_from_ode_compute(od, sta_dim, u_sta_dim, 1.0, dst) + >>> print(dst) + [ 16. 128. 72. 50. 36. 144. 324.] + """ + index: int = 0 + start: Final[int] = ode.shape[1] - 2 + + # from now on, we compute the impact of the state and the controller + add_state: bool = False + last_row: np.ndarray = ode[0] + for i in range(1, len(ode)): + next_row: np.ndarray = ode[i] + weight: float = next_row[-1] - last_row[-1] + inner = start + weight_01: float = weight * gamma + while inner >= state_dim: + v = last_row[inner] + inner -= 1 + dest[index] = (v * v) * weight_01 if -1e100 < v < 1e100 else 1e100 + index += 1 + + if add_state: + inner = use_state_dims # jump to the used state + while inner > 0: + inner -= 1 + v = last_row[inner] + dest[index] = (v * v) * weight if -1e100 < v < 1e100 else 1e100 + index += 1 + + add_state = True + last_row = next_row + + +
+[docs] +def t_from_ode(ode: np.ndarray) -> float: + """ + Get the time sum from an ODE solution. + + The total time that we simulate a system depends on the behavior of the + system. + + :param ode: the ODE solution, as return from :func:`run_ode`. + :return: the total consumed time + + >>> od = np.array([[1, 2, 3, 4, 0.1], + ... [5, 6, 7, 8, 0.2], + ... [9, 6, 4, 3, 0.3], + ... [7, 4, 2, 1, 0.4]]) + >>> print(t_from_ode(od)) + 0.4 + """ + return ode[-1, -1]
+ + + +
+[docs] +def j_from_ode(ode: np.ndarray, state_dim: int, + use_state_dims: int = -1, + gamma: float = 0.1) -> float: + """ + Compute the original figure of merit from an ODE array. + + The figure of merit is the sum of state variable squares plus 0.1 times + the control variable squares. We disregard the state variable values of + the starting states (because they are the same for all controllers on + a given training case and because the control cannot influence them) and + we also disregard the final state and final controller output (as there is + no time slice associated with them, i.e., we only "arrive" in them but + basically spent 0 time in them in our simulation). + + :param ode: the array returned by the ODE function, i.e., :func:`run_ode` + :param state_dim: the state dimension + :param use_state_dims: the dimension until which the state is used, + `-1` for using the complete state + :param gamma: the weight of the controller input + :return: the figure of merit + + >>> od = np.array([[1, 2, 3, 4, 0], + ... [5, 6, 7, 8, 1], + ... [9, 6, 4, 3, 3], + ... [7, 4, 2, 1, 7]]) + >>> sta_dim = 3 + >>> print(f"{j_from_ode(od, sta_dim):.10f}") + 110.0000000000 + >>> print((1.6 + 12.8 + 98 + 72 + 50 + 3.6 + 64 + 144 + 324) / 7) + 110.0 + >>> sta_dim = 3 + >>> print(f"{j_from_ode(od, 3, 2, 0.5):.10f}") + 97.1428571429 + >>> print((8 + 64 + 72 + 50 + 18 + 144 + 324) / 7) + 97.14285714285714 + """ + if len(ode) <= 1: + return 1e200 + # The used state dimension could be equal to the state dimension or less. + # If it is <= 0, then we use the complete state vector + if use_state_dims <= 0: + use_state_dims = state_dim + dest: Final[np.ndarray] = np.empty((ode.shape[0] - 1) * ( + ode.shape[1] - 1 - state_dim + use_state_dims) - use_state_dims) + __j_from_ode_compute(ode, state_dim, use_state_dims, gamma, dest) + return fsum(dest) / ode[-1, -1]
+ + + +
+[docs] +def multi_run_ode( + test_starting_states: Iterable[np.ndarray], + training_starting_states: Iterable[np.ndarray], + collector: Callable[[int, np.ndarray, float, float], None] | Iterable[ + Callable[[int, np.ndarray, float, float], None]], + equations: Callable[[ + np.ndarray, float, np.ndarray, np.ndarray], None], + controller: Callable[[np.ndarray, float, T, np.ndarray], None], + parameters: T, controller_dim: int = 1, + test_steps: int = 5000, + test_time: float = 50.0, + training_steps: int = 5000, + training_time: float = 50.0, + use_state_dims: int = -1, gamma: float = 0.1) -> None: + """ + Invoke :func:`run_ode` multiple times and pass the result to `collector`. + + This function allows us to perform multiple runs of the differential + equation simulator, using different starting points. It also allows us to + distinguish training and test points and to assign them different numbers + of steps. For each of them, :func:`run_ode` will be applied and the + returned matrix is passed to the `collector` function. + + :param test_starting_states: the iterable of test starting states + :param training_starting_states: the iterable of training starting states + :param collector: the destination to receive the results, in the form of + index, ode array, j, and t. + :param equations: the differential system + :param controller: the controller function + :param parameters: the controller parameters + :param controller_dim: the dimension of the controller result + :param test_steps: the number of test steps to simulate + :param test_time: the time limit for tests + :param training_steps: the number of training steps to simulate + :param training_time: the time limit for training + :param use_state_dims: the dimension until which the state is used, + `-1` for using the complete state + :param gamma: the weight of the controller input + """ + if not isinstance(collector, Iterable): + collector = (collector, ) + index: int = 0 + for sp in test_starting_states: + ode = run_ode(sp, equations, controller, parameters, controller_dim, + test_steps, test_time) + for c in collector: + c(index, ode, j_from_ode(ode, len(sp), use_state_dims, gamma), + t_from_ode(ode)) + index += 1 + for sp in training_starting_states: + ode = run_ode(sp, equations, controller, parameters, controller_dim, + training_steps, training_time) + for c in collector: + c(index, ode, j_from_ode(ode, len(sp), use_state_dims, gamma), + t_from_ode(ode)) + index += 1
+ + + +
+[docs] +def diff_from_ode(ode: np.ndarray, state_dim: int) \ + -> tuple[np.ndarray, np.ndarray]: + """ + Compute all the state+control vectors and the resulting differentials. + + This function returns two matrices. Each row of both matrices corresponds + to a time slot. Each row in the first matrix holds the state vector and + the control vector (that was computed by the controller). The + corresponding row in the second matrix then holds the state differential + resulting from the control vector being applied in the differential + equations that govern the system state change. + + The idea is that this function basically provides the data that we would + like to learn when training a surrogate model for a system: From the + current state and the computed control vector, we want that our model can + give us the resulting system differential. If we have such a model and it + works reasonably well, then we could essentially plug this model into + :func:`run_ode` instead of the original `equations` parameter. + + What this function does to compute the differential is to basically + "invert" the dynamic weighting done by :func:`run_ode`. :func:`run_ode` + starts in a given starting state `s`. It then computes the control vector + `c` as a function of `s`, i.e., `c(s)`. Then, the equations of the dynamic + system (see module :mod:`~moptipyapps.dynamic_control.system`) to compute + the state differential `D=ds/dt` as a function of `c(s)` and `s`, i.e., as + something like `D(s, c(s))`. The next step would be to update the state, + i.e., to set `s=s+D(s, c(s))`. Unfortunately, this can make `s` go to + infinity. So :func:`run_ode` will compute a dynamic weight `w` and do + `s=s+w*D(s, c(s))`, where `w` is chosen such that the state vector `s` + does not grow unboundedly. While `s` and `c(s)` and `w` are stored in one + row of the result matrix of :func:`run_ode`, `s+w*D(s,c(s))` is stored as + state `s` in the next row. So what this function here basically does is to + subtract the old state from the next state and divide the result by `w` to + get `D(s, c(s))`. `s` and `c(s)` are already available directly in the ODE + result and `w` is not needed anymore. + + We then get the rows `s, c(s)` and `D(s, c(s))` in the first and second + result matrix, respectively. This can then be used to train a system model + as proposed in model :mod:`~moptipyapps.dynamic_control.system_model`. + + :param ode: the result of :func:`run_ode` + :param state_dim: the state dimensions + :returns: a tuple of the state+control vectors and the resulting + state differential vectors + + >>> od = np.array([ + ... [0, 0, 0, 0, 0, 0], # state 0,0,0; control 0,0; time 0 + ... [1, 2, 3, 4, 5, 1], # state 1,2,3; control 4,5; time 1 + ... [2, 3, 4, 5, 6, 3], # state 2,3,4; control 5,6; time 3 + ... [4, 6, 8, 7, 7, 7]]) # state 4,6,8; control 7,7, time 7 + >>> res = diff_from_ode(od, 3) + >>> res[0] # state and control vectors, time col and last row removed + array([[0, 0, 0, 0, 0], + [1, 2, 3, 4, 5], + [2, 3, 4, 5, 6]]) + >>> res[1] # (state[i + 1] - state[i]) / (time[i + 1] / time[i]) + array([[1. , 2. , 3. ], + [0.5 , 0.5 , 0.5 ], + [0.5 , 0.75, 1. ]]) + """ + return (ode[0:-1, 0:-1], + np.diff(ode[:, 0:state_dim], 1, 0) + / np.diff(ode[:, -1])[:, None])
+ +
diff --git a/_modules/moptipyapps/dynamic_control/results_log.html b/_modules/moptipyapps/dynamic_control/results_log.html new file mode 100644 index 00000000..9c4466cd --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/results_log.html @@ -0,0 +1,120 @@ +moptipyapps.dynamic_control.results_log — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.results_log

+"""A logger for results gathered from ODE integration to a text file."""
+
+from contextlib import AbstractContextManager
+from io import TextIOBase
+from typing import Callable, Final
+
+import numpy as np
+from moptipy.utils.logger import CSV_SEPARATOR
+from moptipy.utils.strings import float_to_str
+from pycommons.io.console import logger
+from pycommons.io.path import Path
+
+
+
+[docs] +class ResultsLog(AbstractContextManager): + """ + A class for logging results via `multi_run_ode`. + + Function :func:`moptipyapps.dynamic_control.ode.multi_run_ode` can pass + its results to various output generating procedures. This class here + offers a procedure for writing them to a log file. + + >>> def projectile(position, ttime, ctrl, out): + ... out[0] = 70.71067811865474 + ... out[1] = 70.71067811865474 - ttime * 9.80665 + >>> param: np.ndarray = np.array([1]) # ignore + >>> def contrl(position, ttime, params, dest): + ... dest[0] = params[0] # controller that returns param + >>> from io import StringIO + >>> from moptipyapps.dynamic_control.ode import multi_run_ode + >>> with StringIO() as sio: + ... with ResultsLog(2, sio) as log: + ... multi_run_ode([np.array([0.0, 1.0])], + ... [np.array([1.0, 1.0])], + ... log.collector, projectile, contrl, param, + ... 1, 10000, 14.890, 10000, 14.961) + ... x=sio.getvalue() + >>> tt = x.split() + >>> print(tt[0]) + figureOfMerit;totalTime;nSteps;start0;start1;end0;end1 + >>> for y in tt[1:]: + ... print(";".join(f"{float(v):.3f}" for v in y.split(";"))) + 403374.924;14.890;10000.000;0.000;1.000;1052.882;-33.244 + 407810.750;14.961;10000.000;1.000;1.000;1058.902;-38.616 + """ + + def __init__(self, state_dim: int, out: TextIOBase | str) -> None: + """ + Create the test results logger. + + :param state_dim: the state dimension + :param out: the output destination + """ + super().__init__() + if isinstance(out, str): + pp = Path(out) + logger(f"logging data to file {pp!r}.") + out = pp.open_for_write() + #: the internal output destination + self.__out: TextIOBase = out + #: the state dimension + self.__state_dim: Final[int] = state_dim + +
+[docs] + def collector(self, index: int, ode: np.ndarray, + j: float, time: float) -> None: + """ + Log the result of a multi-ode run. + + :param index: the index of the result + :param ode: the ode result matrix + :param j: the figure of merit + :param time: the time value + """ + if self.__out is None: + raise ValueError("Already closed output destination!") + out_str: Final[Callable[[str], int]] = self.__out.write + + state_dim: Final[int] = self.__state_dim + if index <= 0: + out_str( + f"figureOfMerit{CSV_SEPARATOR}totalTime{CSV_SEPARATOR}nSteps") + for i in range(state_dim): + out_str(f"{CSV_SEPARATOR}start{i}") + for i in range(state_dim): + out_str(f"{CSV_SEPARATOR}end{i}") + out_str("\n") + + out_str(float_to_str(float(j))) + out_str(CSV_SEPARATOR) + out_str(float_to_str(float(time))) + out_str(CSV_SEPARATOR) + out_str(str(len(ode))) + start = ode[0] + for i in range(state_dim): + out_str(f"{CSV_SEPARATOR}{float_to_str(float(start[i]))}") + end = ode[-1] + for i in range(state_dim): + out_str(f"{CSV_SEPARATOR}{float_to_str(float(end[i]))}") + out_str("\n")
+ + + def __exit__(self, _, __, ___) -> None: + """ + Close this context manager. + + :param _: the exception type; ignored + :param __: the exception value; ignored + :param ___: the exception whatever; ignored + """ + if self.__out is not None: + try: + self.__out.close() + finally: + self.__out = None
+ +
diff --git a/_modules/moptipyapps/dynamic_control/results_plot.html b/_modules/moptipyapps/dynamic_control/results_plot.html new file mode 100644 index 00000000..77ba43d7 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/results_plot.html @@ -0,0 +1,370 @@ +moptipyapps.dynamic_control.results_plot — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.results_plot

+"""
+An illustrator for results gathered from ODE integration to multi-plots.
+
+This evaluator will create figures where each sub-plot corresponds to one
+evolution of the system over time. The starting state is marked with a blue
+cross, the final state with a red one. Both states are connected with a line
+that marks the progress over time. The line begins with blue color and changes
+its color over violet/pinkish towards yellow the farther in time the system
+progresses.
+"""
+
+from contextlib import AbstractContextManager
+from math import isfinite, sqrt
+from os.path import basename, dirname
+from typing import Any, Callable, Collection, Final
+
+import matplotlib as mpl  # type: ignore
+import moptipy.utils.plot_utils as pu
+import numpy as np
+from matplotlib.axes import Axes  # type: ignore
+from matplotlib.figure import Figure  # type: ignore
+from matplotlib.lines import Line2D  # type: ignore
+from moptipy.utils.strings import float_to_str
+from mpl_toolkits.mplot3d.art3d import Line3D  # type: ignore
+from pycommons.io.console import logger
+from pycommons.io.path import Path
+from pycommons.types import check_int_range
+
+
+def _str(f: float) -> str:
+    """
+    Convert a floating point number to a short string.
+
+    :param f: the number
+    :return: the string
+    """
+    f = float(f)
+    a1 = float_to_str(f)
+    a2 = f"{f:.2f}"
+    while a2.endswith("0"):
+        a2 = a2[:-1]
+    if a2.endswith("."):
+        a2 = a2[:-1]
+    if (a1 == "-0") or (a2 == "-0"):
+        return "0"
+    return a1 if len(a1) < len(a2) else a2
+
+
+def _vec(v: np.ndarray) -> str:
+    """
+    Convert a vector to a quick string.
+
+    :param v: the vector
+    :return: the string
+    """
+    s = ",".join(_str(vv) for vv in v)
+    return f"({s})"
+
+
+#: the color maps to use
+__COLOR_MAPS: Final[tuple[str, str, str, str, str, str, str, str, str]] = (
+    "spring", "winter", "copper", "plasma", "cool", "hsv", "winter",
+    "coolwarm", "BrBG")
+
+
+def _get_colors(alen: int, cmi: int = 0, multi: bool = False) -> Callable[
+        [float], tuple[float, float, float, float]]:
+    """
+    Get a given color map with the given number of columns.
+
+    For a given color map index (`cmi`), this function returns a callable that
+    maps values from `[0,1]` to colors. If this is supposed to be the only
+    color map in use (`m̀ulti == False`), then it will return the standard
+    `spring` color map. Otherwise, it will return different color maps
+    depending on the color map index `cmi`.
+
+    :param alen: the number of colors
+    :param cmi: the color map name index
+    :param multi: are there multiple colors
+    :return: the colors
+    """
+    if multi:
+        if cmi == 0:  # pink
+            return lambda x: (0.18 + 0.8 * x, 0.0, 0.18 + 0.8 * x, 1.0)
+        if cmi == 1:  # green
+            return lambda x: (0.0, 0.18 + 0.8 * x, 0.0, 1.0)
+        if cmi == 2:  # blue
+            return lambda x: (0.0, 0.0, 0.18 + 0.8 * x, 1.0)
+        cmi -= 2
+
+    cm: Final[str] = __COLOR_MAPS[
+        check_int_range(cmi, "color_map_index", 0, 10000) % len(__COLOR_MAPS)]
+    obj: Final = _get_colors
+
+    if hasattr(obj, cm):
+        the_colors: dict[int, Callable[[
+            float], tuple[float, float, float, float]]] = getattr(obj, cm)
+    else:
+        the_colors = {}
+        setattr(obj, cm, the_colors)
+    if alen in the_colors:
+        return the_colors[alen]
+    colors: Callable[[float], tuple[float, float, float, float]] \
+        = mpl.colormaps[cm].resampled(alen)
+    the_colors[alen] = colors
+    return colors
+
+
+#: the start point color
+START_COLOR: Final[str] = "blue"
+#: the start marker
+START_MARKER: Final[str] = "++"
+#: the end point color
+END_COLOR: Final[str] = "red"
+#: the end marker
+END_MARKER: Final[str] = "o\u25CF"
+
+
+def _plot_2d(state: np.ndarray, axes: Axes,
+             xi: int, yi: int,
+             colors: Callable[[float], tuple[float, float, float, float]],
+             z: int) -> int:
+    """
+    Plot a figure in 2D.
+
+    :param state: the state matrix
+    :param axes: the plot
+    :param xi: the x-index
+    :param yi: the y-index
+    :param colors: the colors to use
+    :param z: the z index
+    :returns: the new z index
+    """
+    size: Final[int] = len(state) - 1
+    for i in range(size):
+        z += 1
+        axes.add_artist(Line2D(state[i:i + 2, xi],
+                               state[i:i + 2, yi],
+                        color=colors(i / size), zorder=z))
+    z += 1
+    axes.scatter(state[0, xi], state[0, yi], color=START_COLOR,
+                 zorder=z, marker=START_MARKER[0])
+    z += 1
+    axes.scatter(state[-1, xi], state[-1, yi], color=END_COLOR,
+                 zorder=z, marker=END_MARKER[0])
+    return z + 1
+
+
+def _plot_3d(state: np.ndarray, axes: Axes,
+             xi: int, yi: int, zi: int,
+             colors: Callable[[float], tuple[float, float, float, float]],
+             z: int, ranges: list[float]) -> int:
+    """
+    Plot a figure in 3D.
+
+    :param state: the state matrix
+    :param axes: the plot
+    :param xi: the x-index
+    :param yi: the y-index
+    :param yi: the z-index
+    :param colors: the colors to use
+    :param z: the z index
+    :param ranges: the axes ranges
+    :returns: the new z index
+    """
+    if hasattr(axes, "force_zorder"):
+        axes.force_zorder = True
+
+    size: Final[int] = len(state) - 1
+    for i in range(size):
+        z += 1
+        axes.add_artist(Line3D(state[i:i + 2, xi],
+                               state[i:i + 2, yi],
+                               state[i:i + 2, zi],
+                        color=colors(i / size), zorder=z))
+
+    col: str = START_COLOR
+    for p in [state[0], state[-1]]:
+        w: float = 0.04 * ranges[0]
+        z += 1
+        axes.add_artist(Line3D((p[xi] - w, p[xi] + w),
+                               (p[yi], p[yi]),
+                               (p[zi], p[zi]),
+                        color=col, zorder=z))
+        z += 1
+        w = 0.04 * ranges[1]
+        axes.add_artist(Line3D((p[xi], p[xi]),
+                               (p[yi] - w, p[yi] + w),
+                               (p[zi], p[zi]),
+                        color=col, zorder=z))
+        z += 1
+        w = 0.04 * ranges[2]
+        axes.add_artist(Line3D((p[xi], p[xi]),
+                               (p[yi], p[yi]),
+                               (p[zi] - w, p[zi] + w),
+                        color=col, zorder=z))
+        col = END_COLOR
+    return z + 1
+
+
+
+[docs] +class ResultsPlot(AbstractContextManager): + """ + A class for plotting results via `multi_run_ode`. + + Function :func:`moptipyapps.dynamic_control.ode.multi_run_ode` can pass + its results to various output generating procedures. This class here + offers a procedure for plotting them to a file. + """ + + def __init__(self, dest_file: str, sup_title: str | None, + state_dims: int, + plot_indices: Collection[int] = (0, ), + state_dim_mod: int = 0) -> None: + """ + Create the ODE plotter. + + :param dest_file: the path to the destination file + :param sup_title: the title for the figure + :param state_dims: the state dimensions + :param plot_indices: the indices that are supposed to be plotted + :param state_dim_mod: the modulus for the state dimensions + """ + super().__init__() + #: the destination file + self.__dest_file: Final[Path] = Path(dest_file) + logger(f"plotting data to file {self.__dest_file!r}.") + #: the state dimensions + self.__state_dims: Final[int] = check_int_range( + state_dims, "state_dimes", 2, 1_000_000) + #: The modulus for the state dimensions for plotting + self.__state_dim_mod: Final[int] = check_int_range( + state_dim_mod, "state_dim_mod", 0, state_dims) + + total_plots: Final[int] = len(plot_indices) + #: the plot indexes + if not (0 < total_plots <= 9): + raise ValueError(f"Invalid plot indices {plot_indices!r}, " + f"contains {total_plots} elements.") + #: the plot indices + self.__plot_indices: Final[Collection[int]] = plot_indices + + srt: Final[int] = max(3, int(round(1 + sqrt(total_plots)))) + args: Final[dict[str, Any]] = { + "items": total_plots, + "max_items_per_plot": 1, + "max_rows": srt, + "max_cols": srt, + } + use_dims: Final[int] = state_dims if state_dim_mod <= 0 \ + else state_dim_mod + if use_dims >= 3: + args["plot_config"] = {"projection": "3d"} + + figure, plots = pu.create_figure_with_subplots(**args) + #: the figure + self.__figure: Figure | None = figure + #: the plots + self.__subplots: None | tuple[tuple[ + Axes | Figure, int, int, int, int, int], ...] = plots + #: the current plot + self.__cur_plot: int = 0 + #: the z-order + self.__z: int = 0 + + if sup_title is not None: + figure.suptitle(sup_title) + +
+[docs] + def collector(self, index: int, ode: np.ndarray, + j: float, time: float) -> None: + """ + Plot the result of a multi-ode run. + + :param index: the index of the result + :param ode: the ode result matrix + :param j: the figure of merit + :param time: the time value + """ + if self.__figure is None: + raise ValueError("Already closed output figure!") + if index not in self.__plot_indices: + return + + axes: Final[Axes] = pu.get_axes( + self.__subplots[self.__cur_plot][0]) + state_dims: Final[int] = self.__state_dims + state_dim_mod: Final[int] = self.__state_dim_mod + use_dims: Final[int] = state_dims if state_dim_mod <= 0 \ + else state_dim_mod + + # make the appropriate title + v1: str = _vec(ode[0, 0:state_dims]) + v2: str = _vec(ode[-1, 0:state_dims]) + if use_dims == 2: + v1 = f"{START_MARKER[1]}{v1}" + v2 = f"{END_MARKER[1]}{v2}" + lv1: Final[int] = len(v1) + lv2: Final[int] = len(v2) + title: str = f"{v1}\u2192{v2}" if ((lv1 + lv2) < 40) else ( + f"{v1}\u2192\n{v2}" if lv1 <= lv2 else f"{v1}\n\u2192{v2}") + title = f"{title}\nJ={j}, T={time:.4f}" + + state_min: Final[list[float]] = [ + ode[:, i:state_dims:state_dim_mod].min() for i in range(use_dims)] + state_max: Final[list[float]] = [ + ode[:, i:state_dims:state_dim_mod].max() for i in range(use_dims)] + ranges: Final[list[float]] = [ + state_max[i] - state_min[i] for i in range(use_dims)] + + can_plot: bool = all( + isfinite(state_min[i]) and (state_min[i] > -1e20) + and isfinite(state_max[i]) and (state_max[i] < 1e20) + and (state_min[i] < state_max[i]) and isfinite(ranges[i]) + and (0 < ranges[i] < 1e20) for i in range(use_dims)) + + if can_plot: + if (max(1e-20, min(ranges)) / max(ranges)) > 0.25: + axes.set_aspect("equal") + axes.set_xlim(state_min[0], state_max[0]) + axes.set_ylim(state_min[1], state_max[1]) + if (use_dims > 2) and hasattr(axes, "set_zlim"): + axes.set_zlim(state_min[2], state_max[2]) + + size: Final[int] = len(ode) - 2 + for ci, i in enumerate(range(0, state_dims, use_dims)): + colors: Callable[[float], tuple[ + float, float, float, float]] = _get_colors( + size, ci, 0 < state_dim_mod < state_dims) + if use_dims == 3: + self.__z = _plot_3d(ode, axes, i, i + 1, i + 2, colors, + self.__z, ranges) + elif use_dims == 2: + self.__z = _plot_2d(ode, axes, i, i + 1, colors, + self.__z) + else: + raise ValueError(f"Huh? state_dim={state_dims}, " + f"state_dim_mod={state_dim_mod}??") + else: + title = f"{title}\n<cannot plot>" + + axes.set_title(title) + self.__cur_plot += 1
+ + + def __exit__(self, _, __, ___) -> None: + """ + Close this context manager. + + :param _: the exception type; ignored + :param __: the exception value; ignored + :param ___: the exception whatever; ignored + """ + if self.__figure is not None: + try: + the_dir: Final[str] = dirname(self.__dest_file) + the_file: Final[str] = basename(self.__dest_file) + dot: Final[int] = the_file.rindex(".") + prefix: Final[str] = the_file[:dot] + suffix: Final[str] = the_file[dot + 1:] + pu.save_figure(self.__figure, prefix, the_dir, suffix) + logger(f"finished plotting data to {self.__dest_file!r}.") + finally: + self.__figure = None + self.__subplots = None
+ +
diff --git a/_modules/moptipyapps/dynamic_control/starting_points.html b/_modules/moptipyapps/dynamic_control/starting_points.html new file mode 100644 index 00000000..322b5069 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/starting_points.html @@ -0,0 +1,205 @@ +moptipyapps.dynamic_control.starting_points — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.starting_points

+"""
+Synthesize some interesting starting points.
+
+Here we have some basic functionality to synthesize starting points, i.e.,
+training cases, for the dynamic systems control task.
+The points synthesized here by function
+:func:`make_interesting_starting_points` try to fulfill two goals:
+
+1. the points should be as far away from each other as possible in the state
+   space,
+2. there should be points of many different distances from the state space
+   origin, and
+3. compared to the coordinates of the other points, the coordinates of the
+   synthesized points should be sometimes smaller and sometimes larger (where
+   sometimes should ideally mean "equally often").
+
+These two goals are slightly contradicting and are achieved by forcing the
+points to be located on rings of increasing distance from the origin via
+:func:`interesting_point_transform` while maximizing their mean distance
+to each other via :func:`interesting_point_objective`.
+Since :func:`make_interesting_starting_points` is a bit slow, it makes sense
+to pre-compute the points and then store them as array constants.
+"""
+
+from typing import Callable, Final, Iterable, cast
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.algorithms.so.vector.cmaes_lib import BiPopCMAES
+from moptipy.api.execution import Execution
+from moptipy.api.objective import Objective
+from moptipy.spaces.vectorspace import VectorSpace
+from pycommons.io.console import logger
+
+
+
+[docs] +@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) +def interesting_point_transform( + x: np.ndarray, max_radius: float, dim: int) -> np.ndarray: + """ + Transform interesting points. + + >>> xx = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12.0]) + >>> ppp = interesting_point_transform(xx, 10.0, 3) + >>> print(ppp) + [[0.6681531 1.33630621 2.00445931] + [2.27921153 2.84901441 3.41881729] + [3.76928033 4.30774895 4.84621757] + [5.23423923 5.75766315 6.28108707]] + >>> print(xx) + [0.6681531 1.33630621 2.00445931 2.27921153 2.84901441 3.41881729 + 3.76928033 4.30774895 4.84621757 5.23423923 5.75766315 6.28108707] + >>> print([np.sqrt(np.square(pppp).sum()) for pppp in ppp]) + [2.5, 5.0, 7.5, 10.0] + >>> ppp = interesting_point_transform(xx, 10.0, 3) + >>> print(ppp) + [[0.6681531 1.33630621 2.00445931] + [2.27921153 2.84901441 3.41881729] + [3.76928033 4.30774895 4.84621757] + [5.23423923 5.75766315 6.28108707]] + >>> print([np.sqrt(np.square(pppp).sum()) for pppp in ppp]) + [2.5, 5.0, 7.5, 10.0] + >>> ppp = interesting_point_transform(xx, 10.0, 2) + >>> print(ppp) + [[0.74535599 1.49071198] + [2.20132119 2.50305736] + [3.200922 3.8411064 ] + [4.39003072 5.01717796] + [5.66154475 6.11484713] + [6.75724629 7.3715414 ]] + """ + n: Final[int] = len(x) // dim + p: Final[np.ndarray] = np.reshape(x, (n, dim)) + for i in range(n): + pp: np.ndarray = p[i, :] + + cur_radius: float = np.sqrt(np.square(pp).sum()) + if cur_radius <= 0.0: + continue + goal_radius: float = max_radius * ((i + 1) / n) + if goal_radius != cur_radius: + first: bool = True + while True: + old_radius = cur_radius + pp2 = pp * goal_radius / cur_radius + if np.array_equal(pp2, pp): + break + cur_radius = np.sqrt(np.square(pp2).sum()) + if cur_radius == goal_radius: + pp = pp2 + break + if first: + first = False + elif cur_radius > old_radius: + break + pp = pp2 + p[i, :] = pp + return p
+ + + +
+[docs] +@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) +def interesting_point_objective(x: np.ndarray, other: np.ndarray, + max_radius: float, dim: int) -> float: + """ + Compute the point diversity. + + 1. the distance between the points + 2. the distance to the other points + 3. the coordinates in the different dimensions should be equally often + smaller and larger than those of the starting points + """ + pts: np.ndarray = interesting_point_transform(x, max_radius, dim) + n: Final[int] = len(pts) + f: float = 0.0 + score: int = 0 + for i in range(n): + pt: np.ndarray = pts[i] + scale: float = (((i + 1) / n) ** 2) # we know this + for j in range(i + 1, n): + dst: float = np.sqrt(np.square(pt - pts[j]).sum()) + f += scale / dst if dst > 0.0 else 1e10 + for oth in other: + dst = np.sqrt(np.square(pt - oth).sum()) + f += scale / dst if dst > 0.0 else 1e10 + for k, val in enumerate(pt): + score += -1 if val < oth[k] else 1 + + return f * np.log1p(abs(score))
+ + + +
+[docs] +def make_interesting_starting_points( + n: int, other: Iterable[Iterable[float]], + log: bool = True) -> np.ndarray: + """ + Create some reasonably diverse starting points. + + :param n: the number of starting points + :param other: the other points + :param log: write log output + :return: the starting points + + >>> p = make_interesting_starting_points( + ... 3, np.array([[1.0, 2.0], [3.0, 2.9]]), False) + >>> print(",".join(";".join(f"{x:.5f}" for x in row) for row in p)) + 1.77767;0.33029,1.10482;3.44328,3.18515;-4.39064 + + >>> p = make_interesting_starting_points( + ... 3, np.array([[1.0, 2.0, 7.0], [3.0, 2.9, 1.1]]), False) + >>> print(",".join(";".join(f"{x:.5f}" for x in row) for row in p)) + 1.51392;-2.66567;0.86153,3.26154;-5.07757;2.03484,3.75627;5.88214;6.52310 + """ + other_points: Final[np.ndarray] = np.array(other) + dim: Final[int] = other_points.shape[1] + max_fes: Final[int] = 2048 + int(40 * (n ** (dim / 1.7))) + + max_radius: float = max(np.sqrt(np.square(pt).sum()) + for pt in other_points) + max_radius = max(max_radius ** 1.1, max_radius ** 0.6, + max_radius * 1.3) + + max_dim: float = np.max(np.abs( + np.array(other_points).flatten())) + max_dim = max(max_dim ** 1.2, max_dim ** (1.0 / 1.2), + max_dim ** 2.5, max_radius * 1.2) + + if log: + logger( + f"now determining {n} hopefully diverse samples of dimension " + f"{dim} using {len(other_points)} other points, " + f"max_radius={max_radius}, and max_dim={max_dim} for " + f"{max_fes} FEs.") + + space: Final[VectorSpace] = VectorSpace(dim * n, -max_dim, max_dim) + best: np.ndarray = space.create() + + class __Obj(Objective): + def __init__(self): + nonlocal other_points + nonlocal max_radius + nonlocal dim + self.evaluate = cast( # type: ignore + Callable[[np.ndarray], float], + lambda x, o=other_points, mr=max_radius, dd=dim: + interesting_point_objective(x, o, mr, dd)) + + with Execution().set_solution_space(space).set_rand_seed(1)\ + .set_algorithm(BiPopCMAES(space))\ + .set_max_fes(max_fes)\ + .set_objective(__Obj()).execute() as process: + f = process.get_best_f() + process.get_copy_of_best_x(best) + best = interesting_point_transform(best, max_radius, dim) + if log: + logger(f"generated {n} points with objective {f}:\n{best!r}") + return best
+ +
diff --git a/_modules/moptipyapps/dynamic_control/surrogate_optimizer.html b/_modules/moptipyapps/dynamic_control/surrogate_optimizer.html new file mode 100644 index 00000000..a3e73cf5 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/surrogate_optimizer.html @@ -0,0 +1,491 @@ +moptipyapps.dynamic_control.surrogate_optimizer — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.surrogate_optimizer

+"""
+A surrogate system model-based Optimization approach.
+
+In the real world, we want to synthesize a controller `c(s, p)` that can
+drive a dynamic system into a good state. The controller receives as input
+the current state `s`, say, from sensor readings. It can also be
+parameterized by a vector `p`, imagine `c` to be, for example, an artificial
+neural network and then `p` would be its weight vector. The output of `c` will
+influence the system in some way. In our example experiments, this is done
+by becoming part of the state differential `ds/dt`. Anyway, in the real world,
+the controller may steer the rotation speed of a rotor or the angles of rotor
+blades or whatever. Now you can imagine that doing real-world experiments is
+costly and takes a long time. Everytime we want to test a parameterization
+`p`, some form experiment, maybe in a wind tunnel, has to be done.
+
+So it would be beneficial if we could replace the actual experiment by a
+simulation. This would mean that we learn a model `M` that can compute the
+state change `ds/dt` based on the current state `s` and controller output `c`
+at reasonable accuracy. If we had such a computational model, then we could
+run the controller optimization process on that model. Once finished, we could
+apply and evaluate the best controller that we have discovered in a real
+experiment. With the observed behavior of the actual controller system, we may
+even be able to update and improve our system model to become more accurate.
+So we could alternate between real experiments and optimization runs on the
+simulation. Of course, we would always need to do some real experiments at
+first to gather the data to obtain our initial model `M`. But if we can get
+this to work, then we could probably get much better controllers with fewer
+actual experiments.
+
+This here is an algorithm that tries to implement the above pattern.
+This algorithm employs three different sub-algorithms:
+
+1. For sampling the initial :attr:`~SurrogateOptimizer.fes_for_warmup` FEs,
+   it uses a warm-up algorithm - by default, this is done by
+   :mod:`~moptipy.algorithms.random_sampling`.
+2. Then, it spends :attr:`~SurrogateOptimizer.fes_for_training` steps on the
+   collected data to train the model using the model training algorithm,
+   which, by default, is
+   :class:`~moptipy.algorithms.so.vector.cmaes_lib.BiPopCMAES`.
+3. Then, it spends :attr:`~SurrogateOptimizer.fes_per_model_run` steps
+   to train controllers on the model, by default again with a
+   :class:`~moptipy.algorithms.so.vector.cmaes_lib.BiPopCMAES`.
+
+For the model and controller optimization, it thus uses by default the
+BiPop-CMA-ES offered by `moptipy`
+(:class:`~moptipy.algorithms.so.vector.cmaes_lib.BiPopCMAES`).
+But it uses two instances of this algorithm, namely one to optimize the
+controller parameters and one that optimizes the model parameters.
+And, as said, a random sampling method to gather the initial samples.
+
+The idea is that we divide the computational budget into a warmup and a
+model-based phase. In the warmup phase, we use CMA-ES to normally optimize
+the controller based on the actual simulation of the dynamic system.
+However, while doing so, for every time step in the simulation, we collect
+three things: The current state vector `s`, the control vector `c`, and the
+resulting state differential `ds/dt`. Now if we have such data, we can look
+at the dynamic system as a function `D(s, c) = ds/dt`. If we consider the
+dynamic system to be such a function and we have collected the vectors
+`(s, c)` and `ds/dt`, then we may as well attempt to *learn* this system.
+So after the warmup phase, our algorithm does the following: In a loop (for
+the rest of the computational budget), it first tries to learn a model `M` of
+`D`. Then, it replaces the actual differential equations of the system in ODE
+solution approach of the objective function with `M`. In other words, we kick
+out the actual system and instead use the learned system model `M`. We replace
+the differential equations that describe the system using `M`. We can now
+run an entire optimization process on this learned model only, with ODE
+integration and all. This optimization process gives us one new solution which
+we then evaluate on the real objective function (which costs 1 FE and gives us
+a new heap of `(s, c)` and `ds/dt` vectors). With this new data, we again
+learn a new and hopefully more accurate model `M`. This process is iterated
+until the rest of the computational budget is exhausted.
+
+This approach hopefully allows us to learn a model of a dynamic system while
+synthesizing a controller for it. Since we can have infinitely more time to
+synthesize the controller on a learned system model compared to an actual
+model, this may give us much better results.
+
+The starting points of the work here were conversations with Prof. Dr. Bernd
+NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute of Technology in
+Shenzhen, China (哈尔滨工业大学(深圳)).
+"""
+
+
+from copy import copy
+from gc import collect
+from os.path import basename
+from typing import Callable, Final
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.algorithms.random_sampling import RandomSampling
+from moptipy.algorithms.so.vector.cmaes_lib import BiPopCMAES
+from moptipy.api.algorithm import Algorithm
+from moptipy.api.execution import Execution
+from moptipy.api.logging import FILE_SUFFIX
+from moptipy.api.process import Process
+from moptipy.api.subprocesses import for_fes
+from moptipy.operators.vectors.op0_uniform import Op0Uniform
+from moptipy.spaces.vectorspace import VectorSpace
+from moptipy.utils.logger import KeyValueLogSection
+from moptipy.utils.nputils import rand_seed_generate
+from numpy.random import Generator
+from pycommons.io.path import Path
+from pycommons.types import check_int_range, type_error
+
+from moptipyapps.dynamic_control.model_objective import ModelObjective
+from moptipyapps.dynamic_control.objective import FigureOfMerit
+from moptipyapps.dynamic_control.system import System
+from moptipyapps.dynamic_control.system_model import SystemModel
+
+
+def _nop() -> None:
+    """Do absolutely nothing."""
+
+
+def _bpcmaes(vs: VectorSpace) -> BiPopCMAES:
+    """
+    Create the Bi-Pop CMA-ES.
+
+    :param vs: the vector space
+    :return: the algorithm
+    """
+    return BiPopCMAES(vs, True)
+
+
+
+[docs] +class SurrogateOptimizer(Algorithm): + """A surrogate model-based CMA-ES algorithm.""" + + def __init__( + self, system_model: SystemModel, controller_space: VectorSpace, + objective: FigureOfMerit, fes_for_warmup: int, + fes_for_training: int | None = None, + ms_for_training: int | None = None, + fes_per_model_run: int | None = None, + ms_per_model_run: int | None = None, + fancy_logs: bool = False, + warmup_algorithm: Callable[ + [VectorSpace], Algorithm] = + lambda v: RandomSampling(Op0Uniform(v)), + model_training_algorithm: Callable[ + [VectorSpace], Algorithm] = _bpcmaes, + controller_training_algorithm: Callable[ + [VectorSpace], Algorithm] = _bpcmaes) -> None: + """ + Initialize the algorithm. + + :param system_model: the system and model + :param controller_space: the controller space + :param objective: the figure of merit + :param fes_for_warmup: the number of objective function evaluations + (FEs) to be used on the initial stage on the real system for + warming up + :param fes_for_training: the number of FEs to be used to train the + model + :param ms_for_training: the number of milliseconds for model training + :param fes_per_model_run: the number of FEs to be applied to each + optimization run on the model + :param ms_per_model_run: the number of milliseconds for a model run + :param fancy_logs: should we perform fancy logging? + :param warmup_algorithm: the algorithm for sampling the warmup points + :param model_training_algorithm: the model training algorithm builder + :param controller_training_algorithm: the controller training + algorithm builder + """ + super().__init__() + + if not isinstance(system_model, SystemModel): + raise type_error(system_model, "system_model", SystemModel) + if not isinstance(controller_space, VectorSpace): + raise type_error( + controller_space, "controller_space", VectorSpace) + if not isinstance(objective, FigureOfMerit): + raise type_error(objective, "objective", FigureOfMerit) + if not isinstance(fancy_logs, bool): + raise type_error(fancy_logs, "fancy_logs", bool) + if not callable(warmup_algorithm): + raise type_error(warmup_algorithm, "warmup_algorithm", call=True) + if not callable(model_training_algorithm): + raise type_error(model_training_algorithm, + "model_training_algorithm", call=True) + if not callable(controller_training_algorithm): + raise type_error(controller_training_algorithm, + "controller_training_algorithm", call=True) + + # the controller space + self.controller_space: Final[VectorSpace] = controller_space + #: should we do fancy logging? + self.fancy_logs: Final[bool] = fancy_logs + #: the number of objective function evaluations to be used for warmup + self.fes_for_warmup: Final[int] = check_int_range( + fes_for_warmup, "fes_for_warmup", 1, 1_000_000_000) + #: the FEs for training the model + self.fes_for_training: Final[int | None] = None \ + if fes_for_training is None else check_int_range( + fes_for_training, "fes_for_training", 1, 1_000_000_000) + #: the ms for training the model + self.ms_for_training: Final[int | None] = None \ + if ms_for_training is None else check_int_range( + ms_for_training, "ms_for_training", 1, 1_000_000_000_000) + if (self.fes_for_training is None) and (self.ms_for_training is None): + raise ValueError("One of fes_for_training or " + "ms_for_training must not be None.") + #: the FEs for each run on the model + self.fes_per_model_run: Final[int | None] = None \ + if fes_per_model_run is None else check_int_range( + fes_per_model_run, "fes_per_model_run", 1, 1_000_000_000) + #: the ms for each run on the model + self.ms_per_model_run: Final[int | None] = None \ + if ms_per_model_run is None else check_int_range( + ms_per_model_run, "ms_per_model_run", 1, 1_000_000_000_000) + if ((self.fes_per_model_run is None) + and (self.ms_per_model_run is None)): + raise ValueError("One of fes_per_model_run or " + "ms_per_model_run must not be None.") + #: the system model + self.system_model: Final[SystemModel] = system_model + #: the internal warmup algorithm + self.__warmup_algorithm: Final[Algorithm] = warmup_algorithm( + controller_space) + #: the internal controller training algorithm + self.__control_algorithm: Final[Algorithm] = ( + controller_training_algorithm(controller_space)) + #: the model parameter space + self.__model_space: Final[VectorSpace] = \ + system_model.model.parameter_space() + #: the model training algorithm + self.__model_training: Final[Algorithm] = model_training_algorithm( + self.__model_space) + #: the control objective function reference + self.__control_objective: Final[FigureOfMerit] = objective + #: the model objective + self.__model_objective: Final[ModelObjective] = ModelObjective( + objective, system_model.model) + +
+[docs] + def initialize(self) -> None: + """Initialize.""" + super().initialize() + self.__warmup_algorithm.initialize() + self.__model_training.initialize() + self.__control_algorithm.initialize() + self.__model_space.initialize()
+ + +
+[docs] + def solve(self, process: Process) -> None: + """ + Solve the modelling problem. + + This function begins by spending :attr:`fes_for_warmup` objective + function evaluations (FEs) on the actual problem, i.e., by trying to + synthesize controllers for the "real" system, using `process` to + evaluate the controller performance. The objective function passed to + the constructor (an instance of + :class:`~moptipyapps.dynamic_control.objective.FigureOfMerit`) must be + used by `process` as well. This way, during the warm-up phase, we can + collect tuples of the (system state, controller output) and the + resulting system differential for each simulated time step. This is + done via + :meth:`~moptipyapps.dynamic_control.objective.FigureOfMerit.set_raw`. + + After the warm-up phase, we can obtain these collected data via + :meth:`~moptipyapps.dynamic_control.objective.FigureOfMerit.\ +get_differentials`. The data is then used to train a model via the model + objective function + :mod:`~moptipyapps.dynamic_control.model_objective`. The system model + is again basically a + :class:`~moptipyapps.dynamic_control.controller.Controller` which is + parameterized appropriately. For this, we use a CMA-ES algorithm for + :attr:`fes_for_training` FEs. + + Once the model training is completed, we switch the objective function + to use the model instead of the actual system for evaluating + controllers, which is done via :meth:`~moptipyapps.dynamic_control.\ +objective.FigureOfMerit.set_model`. We then train a completely new controller + on the model objective function. Notice that now, the actual system is + not involved at all. We do this again using a CMA-ES algorithm for + :attr:`fes_per_model_run` FEs. + + After training the controller, we can evaluate it on the real system + using the :meth:`~moptipy.api.process.Process.evaluate` method + of the actual `process` (after switching back to the real model via + :meth:`~moptipyapps.dynamic_control.objective.FigureOfMerit.set_raw`). + This nets us a) the actual controller performance and b) a new set of + (system state, controller output) + system state differential tuples. + + Since we now have more data, we can go back and train a new system + model and then use this model for another model-based optimization + run. And so on, and so on. Until the budget is exhausted. + + :param process: the original optimization process, which must use + the `objective` function (an instance of + :class:`~moptipyapps.dynamic_control.objective.FigureOfMerit`) as + its objective function. + """ + # First, we set up the local variables and fast calls. + should_terminate: Final[Callable[[], bool]] = process.should_terminate + model_space: Final[VectorSpace] = self.__model_space + model_objective: Final[ModelObjective] = self.__model_objective + model = model_space.create() + model_equations: Final[Callable[[ + np.ndarray, float, np.ndarray, np.ndarray], None]] =\ + self.system_model.model.controller + raw: Final[FigureOfMerit] = self.__control_objective + random: Final[Generator] = process.get_random() + + training_execute: Final[Execution] = ( + Execution().set_solution_space(model_space) + .set_algorithm(self.__model_training) + .set_objective(model_objective)) + if self.fes_for_training is not None: + training_execute.set_max_fes(self.fes_for_training) + if self.ms_for_training is not None: + training_execute.set_max_time_millis(self.ms_for_training) + + on_model_execute: Final[Execution] = \ + (Execution().set_solution_space(self.controller_space) + .set_objective(raw) + .set_algorithm(self.__control_algorithm)) + if self.fes_per_model_run is not None: + on_model_execute.set_max_fes(self.fes_per_model_run) + if self.ms_per_model_run is not None: + on_model_execute.set_max_time_millis(self.ms_per_model_run) + + result: Final[np.ndarray] = self.controller_space.create() + orig_init: Callable = raw.initialize + +# Get a log dir if logging is enabled and set up all the logging information. + log_dir_name: str | None = process.get_log_basename() \ + if self.fancy_logs else None + model_training_dir: Path | None = None + model_training_log_name: str | None = None + models_dir: Path | None = None + models_name: str | None = None + tempsys: System | None = None + ctrl_dir: Path | None = None + ctrl_log_name: str | None = None + controllers_dir: Path | None = None + controllers_name: str | None = None + if log_dir_name is not None: + log_dir: Final[Path] = Path(log_dir_name) + log_dir.ensure_dir_exists() + prefix: str = "modelTraining" + model_training_dir = log_dir.resolve_inside(prefix) + model_training_dir.ensure_dir_exists() + base_name: Final[str] = basename(log_dir_name) + model_training_log_name = f"{base_name}_{prefix}_" + training_execute.set_log_improvements(True) + prefix = "controllerOnModel" + ctrl_dir = log_dir.resolve_inside(prefix) + ctrl_dir.ensure_dir_exists() + ctrl_log_name = f"{base_name}_{prefix}_" + on_model_execute.set_log_improvements(True) + prefix = "model" + models_dir = log_dir.resolve_inside(prefix) + models_dir.ensure_dir_exists() + tempsys = copy(self.system_model.system) + models_name = f"{tempsys.name}_{prefix}_" + prefix = "controllersOnReal" + controllers_dir = log_dir.resolve_inside(prefix) + controllers_dir.ensure_dir_exists() + controllers_name = f"{base_name}_{prefix}_" + +# Now we do the setup run that creates some basic results and +# gathers the initial information for modelling the system. + with for_fes(process, self.fes_for_warmup) as prc: + self.__warmup_algorithm.solve(prc) + + while not should_terminate(): # until budget exhausted + consumed_fes: int = process.get_consumed_fes() + + # We now train a model on the data that was gathered. + training_execute.set_rand_seed(rand_seed_generate(random)) + if model_training_dir is not None: + training_execute.set_log_file( + model_training_dir.resolve_inside( + f"{model_training_log_name}{consumed_fes}" + f"{FILE_SUFFIX}")) + model_objective.begin() # get the collected data + with training_execute.execute() as sub: # train model + sub.get_copy_of_best_y(model) # get best model + model_objective.end() # dispose the collected data + + setattr(raw, "initialize", _nop) # prevent resetting to "raw" + +# The trained model is wrapped into an equation function that can be passed to +# the ODE integrator. + @numba.njit(cache=False, inline="always", fastmath=True, + boundscheck=False) + def __new_model(state: np.ndarray, time: float, + control: np.ndarray, out: np.ndarray, + _params=model, _eq=model_equations) -> None: + _eq(np.hstack((state, control)), time, _params, out) + + setattr(__new_model, "modelParameters", model) # see objective + + if tempsys is not None: # plot the model behavior + tempsys.equations = __new_model # type: ignore + setattr(tempsys, "name", f"{models_name}{consumed_fes}") + tempsys.describe_system_without_control(models_dir) + + collect() # now we collect all garbage ... there should be much + +# OK, now that we got the model, we can perform the model optimization run. + raw.set_model(__new_model) # switch to use the model + on_model_execute.set_rand_seed(rand_seed_generate(random)) + if ctrl_dir is not None: + on_model_execute.set_log_file(ctrl_dir.resolve_inside( + f"{ctrl_log_name}{consumed_fes}{FILE_SUFFIX}")) + with on_model_execute.execute() as ome: + ome.get_copy_of_best_y(result) # get best controller + raw.set_raw() # switch to the actual problem and data collection + setattr(raw, "initialize", orig_init) # allow resetting to "raw" + + if tempsys is not None: # plot the controller on that model + setattr(tempsys, "name", f"{models_name}{consumed_fes}") + tempsys.describe_system( + f"{models_name}{consumed_fes}", + self.system_model.controller.controller, result, + f"{models_name}{consumed_fes}_synthesized_controller", + models_dir) + +# Finally, we re-evaluate the result that we got from the model run on the +# actual objective function. + process.evaluate(result) # get the real objective value + +# plot the actual behavior + if controllers_dir is not None: + self.system_model.system.describe_system( + f"{self.system_model.system}_{consumed_fes}", + self.system_model.controller.controller, + result, f"{controllers_name}{consumed_fes}", + controllers_dir) + + collect() # now we collect all garbage ... there should be much
+ + + def __str__(self): + """ + Get the name of the algorithm. + + :return: the algorithm name + """ + s: str = (f"{self.__warmup_algorithm}_{self.__control_algorithm}_" + f"{self.__model_training}_{self.fes_for_warmup}_tn") + if self.fes_for_training is not None: + s = f"{s}_{self.fes_for_training}fes" + if self.ms_for_training is not None: + s = f"{s}_{self.ms_for_training}ms" + s = f"{s}_sy" + if self.fes_per_model_run is not None: + s = f"{s}_{self.fes_per_model_run}fes" + if self.ms_per_model_run is not None: + s = f"{s}_{self.ms_per_model_run}ms" + return s + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log all parameters of this component as key-value pairs. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + logger.key_value("fesForWarmup", self.fes_for_warmup) + logger.key_value("fesForTraining", self.fes_for_training) + logger.key_value("fesPerModelRun", self.fes_per_model_run) + logger.key_value("fancyLogs", self.fancy_logs) + with logger.scope("controlX") as cx: + self.controller_space.log_parameters_to(cx) + with logger.scope("warmupA") as wa: + self.__warmup_algorithm.log_parameters_to(wa) + with logger.scope("controlA") as ca: + self.__control_algorithm.log_parameters_to(ca) + with logger.scope("controlF") as cf: + self.__control_objective.log_parameters_to(cf) + with logger.scope("modelX") as mx: + self.__model_space.log_parameters_to(mx) + with logger.scope("modelF") as mf: + self.__model_objective.log_parameters_to(mf) + with logger.scope("modelA") as ma: + self.__model_training.log_parameters_to(ma)
+
+ +
diff --git a/_modules/moptipyapps/dynamic_control/system.html b/_modules/moptipyapps/dynamic_control/system.html new file mode 100644 index 00000000..d9efe48c --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/system.html @@ -0,0 +1,379 @@ +moptipyapps.dynamic_control.system — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.system

+"""
+A class to model a dynamic system governed by differential equations.
+
+A system has a current state vector at any point in time. The state changes
+over time based on differential equations. These equations can be influenced
+by the output of a :mod:`~moptipyapps.dynamic_control.controller`. Our
+:class:`System` presents the state dimension and differential equations. It
+also presents several starting states for simulating the system. The starting
+states are divided into training and testing states. The training states can
+be used for, well, training controllers to learn how to handle the system.
+The testing states are then used to verify whether a synthesized controller
+actually works.
+
+Examples for different dynamic systems are given in package
+:mod:`~moptipyapps.dynamic_control.systems`.
+"""
+
+from math import isfinite
+from typing import Any, Callable, Final, Iterable, TypeVar
+
+import moptipy.utils.plot_utils as pu
+import numpy as np
+from moptipy.api.component import Component
+from moptipy.utils.logger import KeyValueLogSection
+from moptipy.utils.nputils import array_to_str
+from pycommons.io.path import Path
+from pycommons.types import check_int_range, type_error
+
+from moptipyapps.dynamic_control.ode import multi_run_ode
+from moptipyapps.dynamic_control.results_log import ResultsLog
+from moptipyapps.dynamic_control.results_plot import ResultsPlot
+
+#: the color for training points
+_TRAINING_COLOR: Final[str] = "orange"
+#: the color for testing points
+_TEST_COLOR: Final[str] = "cyan"
+
+#: the type variable for ODE controller parameters
+T = TypeVar("T")
+
+
+def _dummy_ctrl(_: np.ndarray, __: float, ___: Any,
+                out: np.ndarray) -> None:
+    """Do nothing as internal dummy controller that does nothing."""
+    out.fill(0.0)
+
+
+
+[docs] +class System(Component): + """A class for governing a system via differential system.""" + + def __init__(self, name: str, state_dims: int, control_dims: int, + state_dim_mod: int, state_dims_in_j: int, gamma: float, + test_starting_states: np.ndarray, + training_starting_states: np.ndarray, + test_steps: int = 5000, + test_time: float = 50.0, + training_steps: int = 1000, + training_time: float = 50.0, + plot_examples: Iterable[int] = (0, )) -> None: + """ + Initialize the system. + + :param name: the name of the system. + :param state_dims: the state dimensions + :param control_dims: the control dimensions + :param state_dim_mod: the modulus for the state dimensions + :param state_dims_in_j: the dimension until which the state is used in + the computation of the figure of merit; `-1` for using the complete + state + :param gamma: the weight of the controller input + :param test_starting_states: the starting states to be used for + testing, as a matrix with one point per row + :param test_steps: the steps to be taken for the test simulation + :param test_time: the time limit for tests + :param training_steps: the steps to be taken for the training + simulation + :param training_time: the time limit for training + :param training_starting_states: the starting states to be used for + training, as a matrix with one point per row + :param plot_examples: the points that should be plotted + """ + super().__init__() + if not isinstance(name, str): + raise type_error(name, "name", str) + #: the name of the model + self.name: Final[str] = name + #: the dimensions of the state variable + self.state_dims: Final[int] = check_int_range( + state_dims, "state_dims", 2, 1_000_000) + #: the dimensions of the controller output + self.control_dims: Final[int] = check_int_range( + control_dims, "control_dims", 1, 100) + #: The modulus for the state dimensions for plotting + self.state_dim_mod: Final[int] = check_int_range( + state_dim_mod, "state_dim_mod", 0, state_dims) + state_dims_in_j = check_int_range( + state_dims_in_j, "state_dims_in_j", -1, state_dims) + #: The number of dimensions used in the J computation + self.state_dims_in_j: Final[int] = state_dims if state_dims_in_j <= 0 \ + else state_dims_in_j + if not isinstance(gamma, float): + raise type_error(gamma, "gamma", float) + if (not isfinite(gamma)) and gamma > 0.0: + raise ValueError( + f"gamma must be positive and finite, but is {gamma}.") + #: The Weight of the control values in the figure of merit computation. + self.gamma: Final[float] = gamma + + if not isinstance(test_starting_states, np.ndarray): + raise type_error(test_starting_states, "test_starting_states", + np.ndarray) + if (len(test_starting_states) < 1) or ( + test_starting_states.shape[1] != state_dims): + raise ValueError( + f"invalid test starting states {test_starting_states!r}.") + #: the test starting states + self.test_starting_states: Final[np.ndarray] = test_starting_states + + if not isinstance(training_starting_states, np.ndarray): + raise type_error(training_starting_states, + "training_starting_states", np.ndarray) + if (len(training_starting_states) < 1) or ( + training_starting_states.shape[1] != state_dims): + raise ValueError("invalid training starting " + f"states {training_starting_states!r}.") + #: the test starting states + self.training_starting_states: Final[np.ndarray] = \ + training_starting_states + + #: the test simulation steps + self.test_steps: Final[int] = check_int_range( + test_steps, "test_steps", 10, 1_000_000_000) + + if not isinstance(test_time, float): + raise type_error(test_time, "test_time", float) + if (not isfinite(test_time)) and test_time > 1e-5: + raise ValueError( + f"test_time must be > 1e-5 and finite, but is {test_time}.") + #: the test time + self.test_time: Final[float] = test_time + + #: the training simulation steps + self.training_steps: Final[int] = check_int_range( + training_steps, "training_steps", 10, 1_000_000_000) + if not isinstance(training_time, float): + raise type_error(training_time, "training_time", float) + if (not isfinite(training_time)) and training_time > 1e-5: + raise ValueError(f"training_time must be > 1e-5 and finite, " + f"but is {training_time}.") + #: the training time + self.training_time: Final[float] = training_time + + #: the plot examples + self.plot_examples: Final[tuple[int, ...]] = tuple(sorted(set( + plot_examples))) + total: Final[int] = len(self.training_starting_states) \ + + len(self.test_starting_states) - 1 + for i in self.plot_examples: + check_int_range(i, "plot_examples[i]", 0, total) + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log all parameters of this component as key-value pairs. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + logger.key_value("stateDims", self.state_dims) + logger.key_value("controlDims", self.control_dims) + logger.key_value("stateDimMod", self.state_dim_mod) + logger.key_value("stateDimsInJ", self.state_dims_in_j) + logger.key_value("gamma", self.gamma) + logger.key_value("testStartingStates", + array_to_str(self.test_starting_states.flatten())) + logger.key_value( + "trainingStartingStates", + array_to_str(self.training_starting_states.flatten())) + logger.key_value("testSteps", self.test_steps) + logger.key_value("testTime", self.test_time) + logger.key_value("trainingSteps", self.training_steps) + logger.key_value("trainingTime", self.training_time) + logger.key_value("examplePlots", array_to_str(np.array( + self.plot_examples, int).flatten()))
+ + + def __str__(self): + """ + Get the name of this model. + + :return: the name of this model + """ + return self.name + +
+[docs] + def equations(self, state: np.ndarray, time: float, + control: np.ndarray, out: np.ndarray) -> None: + """ + Compute the values of the differential equations to be simulated. + + :param state: the state of the system + :param time: the time index + :param control: the output of the controller + :param out: the differential, i.e., the output of this function + """
+ + +
+[docs] + def plot_points(self, dest_dir: str, skip_if_exists: bool = True) -> Path: + """ + Plot the training and testing points of the equation. + + :param dest_dir: the destination directory + :param skip_if_exists: `True` to skip existing files, `False` otherwise + :return: the plotted file + """ + name: Final[str] = f"{self.name}_points" + dest_folder: Final[Path] = Path(dest_dir) + dest_folder.ensure_dir_exists() + dest_file: Final[Path] = dest_folder.resolve_inside(f"{name}.pdf") + if dest_file.ensure_file_exists() and skip_if_exists: + return dest_file + + figure = pu.create_figure() + state_dims: Final[int] = self.state_dims + state_dim_mod: Final[int] = self.state_dim_mod + use_dims: Final[int] = state_dims if state_dim_mod <= 0 \ + else state_dim_mod + if use_dims == 3: + axes = figure.add_subplot(projection="3d") + axes.set_aspect("equal") + if hasattr(axes, "force_zorder"): + axes.force_zorder = True + if hasattr(axes, "set_zlabel"): + axes.set_zlabel("z") + + if state_dims == 3: + axes.scatter(self.training_starting_states[:, 0], + self.training_starting_states[:, 1], + self.training_starting_states[:, 2], + color=_TRAINING_COLOR, marker="x", + zorder=1) + axes.scatter(self.test_starting_states[:, 0], + self.test_starting_states[:, 1], + self.test_starting_states[:, 2], + color=_TEST_COLOR, marker="o", zorder=2) + axes.scatter(0, 0, 0, color="black", marker="+", zorder=3) + else: + raise NotImplementedError + + if hasattr(axes, "set_zlim"): + if not hasattr(axes, "get_zlim"): + raise ValueError("Axes is invalid, does not have " + "'get_zlim' but 'set_zlim'.") + zlim = axes.get_zlim() # type: ignore + axes.set_zlim(1.1 * zlim[0], 1.1 * zlim[1]) + elif use_dims == 2: + axes = pu.get_axes(figure) + axes.set_aspect("equal") + axes.grid(True) + if state_dims == 2: + axes.scatter(self.training_starting_states[:, 0], + self.training_starting_states[:, 1], + color=_TRAINING_COLOR, marker="x", + zorder=1) + axes.scatter(self.test_starting_states[:, 0], + self.test_starting_states[:, 1], + color=_TEST_COLOR, marker="o", zorder=2) + else: + markers: Final[list[str]] = list("xvo^+<s>") + colors: Final[list[str]] = [_TRAINING_COLOR, _TEST_COLOR] + zorder: int = 1 + for i, data in enumerate([self.training_starting_states, + self.test_starting_states]): + color = colors[i] + for row in data: + x: np.ndarray = row[0:state_dims:state_dim_mod] + y: np.ndarray = row[1:state_dims:state_dim_mod] + zorder += 1 + axes.plot(x, y, color=color, zorder=zorder) + for k, xx in enumerate(x): + zorder += 1 + axes.scatter(xx, y[k], color=color, + zorder=zorder, + marker=markers[k % len(markers)]) + else: + raise ValueError(f"invalid dimensions {state_dims} " + f"with modulus {state_dim_mod} for {self}.") + + xlim = axes.get_xlim() + axes.set_xlim(1.1 * xlim[0], 1.1 * xlim[1]) + ylim = axes.get_ylim() + axes.set_ylim(1.1 * ylim[0], 1.1 * ylim[1]) + axes.set_title(f"{self.name} system\ntraining points (" + f"{_TRAINING_COLOR}), test points ({_TEST_COLOR})") + + axes.set_xlabel("x") + axes.set_ylabel("y") + res = pu.save_figure(figure, name, dest_folder, "pdf")[0] + if res != dest_file: + raise ValueError( + f"Wrong destination file {res!r}!={dest_file!r}?") + return dest_file
+ + +
+[docs] + def describe_system( + self, title: str | None, + controller: Callable[[np.ndarray, float, T, np.ndarray], None], + parameters: T, base_name: str, dest_dir: str, + skip_if_exists: bool = False) \ + -> tuple[Path, ...]: + """ + Describe the performance of a given system of system. + + :param title: the title + :param controller: the controller to simulate + :param parameters: the controller parameters + :param base_name: the base name of the file to produce + :param dest_dir: the destination directory + :param skip_if_exists: if the file already exists + :returns: the paths of the generated files + """ + dest: Final[Path] = Path(dest_dir) + dest.ensure_dir_exists() + + the_title = f"{self.name} system" + if title is not None: + the_title = f"{the_title}: {title}" + + file_1 = dest.resolve_inside(f"{base_name}_plot.pdf") + file_2 = dest.resolve_inside(f"{base_name}_results.csv") + if file_1.ensure_file_exists() and file_2.ensure_file_exists() \ + and skip_if_exists: + return file_1, file_2 + + with ResultsPlot(dest_file=file_1, sup_title=the_title, + state_dims=self.state_dims, + plot_indices=self.plot_examples, + state_dim_mod=self.state_dim_mod) as plt, \ + ResultsLog(self.state_dims, file_2) as log: + multi_run_ode( + self.test_starting_states, + self.training_starting_states, + (plt.collector, log.collector), + self.equations, + controller, parameters, self.control_dims, + self.test_steps, self.test_time, + self.training_steps, self.training_time, + self.state_dims_in_j, self.gamma) + + return file_1, file_2
+ + +
+[docs] + def describe_system_without_control(self, dest_dir: str, + skip_if_exists: bool = True) \ + -> tuple[Path, ...]: + """ + Describe the performance of a given system of system. + + :param dest_dir: the destination directory + :param skip_if_exists: if the file already exists + :returns: the paths of the generated files + """ + return self.describe_system("without control", _dummy_ctrl, 0.0, + f"{self.name}_without_control", dest_dir, + skip_if_exists)
+
+ +
diff --git a/_modules/moptipyapps/dynamic_control/system_model.html b/_modules/moptipyapps/dynamic_control/system_model.html new file mode 100644 index 00000000..39c9cc4e --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/system_model.html @@ -0,0 +1,121 @@ +moptipyapps.dynamic_control.system_model — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.system_model

+"""
+An extended dynamic control problem `Instance` with a model for the dynamics.
+
+An :class:`~moptipyapps.dynamic_control.instance.Instance` is a combination of
+
+- a set of differential equations
+  (:mod:`~moptipyapps.dynamic_control.system`) that govern the change
+  `D=ds/dt` of the state `s` of a dynamic system (based on its current
+  state `s` and the controller output `c(s)`), i.e., `ds/dt=D(s,c(s))` and
+- a blueprint `c(s, p)` of the controller `c(s)`
+  (:mod:`~moptipyapps.dynamic_control.controller`).
+
+The blueprint of the controller is basically a function that can be
+parameterized, so it is actually a function `c(s, p)` and the goal of
+optimization is to parameterize it in such a way that the figure of merit,
+i.e., the objective value (:mod:`~moptipyapps.dynamic_control.objective`)
+of the system (usually the sum of squared state values and squared controller
+outputs) is minimized. Parameterization means finding good values `p` such
+that the above goal is reached. In other words, we want to synthesize a
+controller (by finding good values of `p`) in such a way that the state
+equations drive the system into a stable state, usually ideally the origin of
+the coordinate system.
+
+Now here this :class:`~moptipyapps.dynamic_control.instance.Instance` is
+extended to a :class:`~moptipyapps.dynamic_control.system_model.SystemModel`
+by adding a parameterized model `M(s, c(s), q)` to the mix. The idea is to
+develop the parameterization `q` of the model `M` that can replace the actual
+system equations `D`. Such a model receives as input the current system state
+vector `s` and the controller output `c(s)` for the state vector `s`. It will
+return the differential `D` of the system state, i.e., `ds/dt`. In other
+words, a properly constructed model can replace the `equations` parameter in
+the ODE integrator :func:`~moptipyapps.dynamic_control.ode.run_ode`. The
+input used for training is provided by
+:func:`~moptipyapps.dynamic_control.ode.diff_from_ode`.
+
+What we do here is to re-use the same function models as used in controllers
+(:mod:`~moptipyapps.dynamic_control.controller`) and learn their
+parameterizations from the observed data. If successful, we can  wrap
+everything together into a `Callable` and plug it into the system instead of
+the original equations.
+
+The thing that :class:`SystemModel` offers is thus a blueprint of the model
+`M`. Obviously, we can conceive many different such blueprints. We could have
+a linear model, a quadratic model, or maybe neural network (in which case, we
+need to decide about the number of layers and layer sizes). So an instance of
+this surrogate model based approach has an equation system, a controller
+blueprint, and a model blueprint.
+
+An example implementation of the concept of synthesizing models for a dynamic
+system in order to synthesize controllers is given as the
+:mod:`~moptipyapps.dynamic_control.surrogate_optimizer` algorithm.
+Examples for different dynamic systems controllers (which we here also use to
+model the systems themselves) are given in package
+:mod:`~moptipyapps.dynamic_control.controllers`.
+
+The starting points of the work here were conversations with Prof. Dr. Bernd
+NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute of Technology in
+Shenzhen, China (哈尔滨工业大学(深圳)).
+"""
+from typing import Final
+
+from moptipy.utils.logger import KeyValueLogSection
+from pycommons.types import type_error
+
+from moptipyapps.dynamic_control.controller import Controller
+from moptipyapps.dynamic_control.instance import Instance
+from moptipyapps.dynamic_control.system import System
+
+
+
+[docs] +class SystemModel(Instance): + """A dynamic system control `Instance` with a system model blueprint.""" + + def __init__(self, system: System, controller: Controller, + model: Controller) -> None: + """ + Initialize the system model. + + :param system: the system that we want to model + :param controller: the controller that we want to use + :param model: a system model blueprint (also in the shape of a + controller) + """ + if not isinstance(model, Controller): + raise type_error(model, "model", Controller) + super().__init__(system, controller, model.name) + in_dims: Final[int] = system.state_dims + controller.control_dims + if model.state_dims != in_dims: + raise ValueError( + f"model.state_dims={model.state_dims}, but system.state_dims" + f"={system.state_dims} and controller.control_dims=" + f"{controller.control_dims}, so we expected {in_dims} for " + f"controller {str(controller)!r}, system {str(system)!r}, and" + f" model {str(model)!r}.") + if model.control_dims != system.state_dims: + raise ValueError( + f"model.control_dims={model.control_dims} must be the same as" + f" system.state_dims={system.state_dims}, but is not, for " + f"controller {str(controller)!r}, system {str(system)!r}, and" + f" model {str(model)!r}.") + #: the model blueprint that can be trained to hopefully replace the + #: :attr:`~moptipyapps.dynamic_control.instance.Instance.system` in the + #: ODE integration / system simulation procedure + self.model: Final[Controller] = model + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log all parameters of this component as key-value pairs. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + with logger.scope("model") as scope: + self.model.log_parameters_to(scope)
+
+ +
diff --git a/_modules/moptipyapps/dynamic_control/systems/.nojekyll b/_modules/moptipyapps/dynamic_control/systems/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/moptipyapps/dynamic_control/systems/lorenz.html b/_modules/moptipyapps/dynamic_control/systems/lorenz.html new file mode 100644 index 00000000..50429dd9 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/systems/lorenz.html @@ -0,0 +1,204 @@ +moptipyapps.dynamic_control.systems.lorenz — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.systems.lorenz

+"""
+The three-dimensional Lorenz system.
+
+The initial starting point of the work here were conversations with
+Prof. Dr. Bernd NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute
+of Technology in Shenzhen, China (哈尔滨工业大学(深圳)) as well as the
+following two MSc theses and book:
+
+1. Yuxiang LI (李宇翔). Jet Mixing Enhancement using Deep Reinforcement
+   Learning (基于深度强化学习的射流混合增强控制). MSc Thesis. Harbin Institute
+   of Technology in Shenzhen, China (哈尔滨工业大学(深圳)).
+   January 2023.
+2. Wei SUN (孙伟). Wake Control of 1-2-3 Fluidic Pinball using Deep
+   Reinforcement Learning (基于深度强化学习方法的 1-2-3 流体弹球尾流控制).
+   MSc Thesis. Harbin Institute of Technology in Shenzhen, China
+   (哈尔滨工业大学(深圳)). January 2023.
+3. Guy Yoslan CORNEJO MACEDA, François LUSSEYRAN, and Bernd R. NOACK.
+   xMLC: A Toolkit for Machine Learning Control, First Edition.
+   Machine Learning Tools in Fluid Mechanics, Vol 2.
+   Shenzhen & Paris; Universitätsbibliothek der Technischen Universität
+   Braunschweig. 2022 https://doi.org/10.24355/dbbs.084-202208220937-0
+"""
+
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+from pycommons.types import check_int_range
+
+from moptipyapps.dynamic_control.starting_points import (
+    make_interesting_starting_points,
+)
+from moptipyapps.dynamic_control.system import System
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __lorenz_equations(state: np.ndarray, _: float,
+                       control: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute the differential equations of a controlled 3D Lorenz system.
+
+    :param state: the state of the system
+    :param _: the time index, which is ignored
+    :param control: the output of the controller
+    :param out: the differential, i.e., the output of this function
+    """
+    x: Final[float] = state[0]
+    y: Final[float] = state[1]
+    z: Final[float] = state[2]
+
+    # compute the differential system
+    out[0] = 10.0 * (y - x)
+    out[1] = 28.0 * x - y - (x * z) + control[0]
+    out[2] = x * y - (2.6666666666666665 * z)
+
+
+
+[docs] +def make_lorenz(n_points: int) -> System: + """ + Create the Lorenz system. + + :param n_points: the number of training points + :return: the Lorenz system + """ + check_int_range(n_points, "n_points", 1, 1_000) + tests: Final[np.ndarray] = \ + np.array([[-1.0, 1.0, 3.0], [0.0, 1.0, 22.0]], float) + # similar to training = make_interesting_starting_points(111, tests) + training: Final[np.ndarray] = np.array( + [[-2.31837616e-1, -5.24709723e-2, -1.28671997e-1], + [5.24425115e-1, -4.36886027e-2, 1.23705573e-1], + [-3.34157047e-1, 6.24361532e-1, 3.95015241e-1], + [-4.55212674e-2, -9.84129335e-1, -4.45366280e-1], + [3.63753119e-1, 5.10336447e-1, -1.19737193], + [-2.09587174e-1, -6.89638430e-1, 1.45278120], + [-1.83653628, 3.19596142e-1, -3.23809729e-1], + [1.15939504, 1.50893034, 1.02697020], + [1.97903772, -1.41455814, 1.27529901e-2], + [-8.62639452e-1, 2.43142041, -8.06154187e-1], + [-1.69664567, -2.39320251, -4.83774211e-1], + [2.73331044, 8.57709170e-1, -1.52110281], + [5.33078125e-2, -1.31842985, -3.25664933], + [-8.07769100e-2, -2.97754587, 2.33390990], + [2.35072569, -1.43006329e-1, 3.30026821], + [-4.03232742, -8.31922303e-1, 1.32332254], + [-3.24522964, -1.89918862e-2, -3.25299734], + [1.55208382, 4.52665580, 8.78256040e-1], + [2.22089300, -4.58186003, -6.69524512e-1], + [1.00280557, 2.54311243, -4.66372240], + [-4.04118291, 3.70295973, 1.47501504], + [5.67173423, 1.44565665, 1.04964605], + [-2.59523889, -5.26245489, -2.05406409], + [2.94504196, -2.45896263, -5.23085552], + [-3.28897946, 4.83504710, -3.38620915], + [-2.58817813, -6.25659365e-1, 6.50364050], + [5.72357009, -3.49153015, 2.88262233], + [5.22299795, 4.53644041, -3.06899979], + [-4.87825184, -5.29004044, 3.10808896], + [4.27501083, 3.63860787, 5.85126863], + [-8.34000452, 4.25671605e-1, -6.87181237e-1], + [-1.08540415, 8.10559431, 2.81655916], + [1.94788569, -5.30894243, 6.89792956], + [8.45383153, -1.55041688, -3.25336920], + [-1.03169607, 1.91837732, -9.20608115], + [-6.59584225, -4.08814508, -5.87073907], + [5.15768660e-2, -7.31844351, -6.81576316], + [1.10412724, 9.35404100, -4.09623004], + [-9.24919988e-1, -1.04001359e+1, 1.45033454], + [-8.42728582, 4.69942357, 4.87746425], + [6.56849521, -8.92524514, 7.73503710e-2], + [-7.84621150, 3.63654240, -7.35441923], + [-1.76374875, 5.20695220, 1.02402030e+1], + [6.59956499, 2.63679539, -9.53593460], + [1.11970782e+1, 4.34954429e-1, 4.73069196], + [8.21196486, 9.31990914, 5.42613335e-1], + [-6.86424196, 1.06609923e+1, -7.81891961e-1], + [-4.09122371, -5.09173842, 1.12099315e+1], + [-1.12617383e+1, -3.36092053, 6.10660311], + [4.40559872, 1.77756117e-1, 1.27751719e+1], + [-8.84888642, -1.05672816e+1, 2.33623289e-1], + [3.10452705, 1.18665218e+1, 6.86275419], + [7.72028081, -7.37696538, -9.54979561], + [-2.05792588, -3.26013517, -1.40774520e+1], + [-1.43317322e+1, -2.67395566, -2.90739935], + [9.70967854, -7.60857398, 8.77170340], + [1.33595404e+1, 5.64205226, -5.20159173], + [1.84184161, 9.60079349, -1.22556406e+1], + [1.45763613e+1, -6.18659546, -1.88994719], + [3.57766009, -1.50015844e+1, -5.01626682], + [9.17805707e-1, -1.39139085e+1, 8.79834996], + [1.06480184e+1, 6.70055614, 1.10706193e+1], + [-1.56128381e+1, 6.46573099, -2.09815734], + [-9.87640379, -8.30988969, -1.15173542e+1], + [-9.95414070, 4.28155228, 1.38294971e+1], + [-6.89378192, 1.36522586e+1, -9.18334631], + [1.76686982, 1.79486554e+1, -1.63796984], + [7.05633216, -1.36984415, -1.69160748e+1], + [-6.42179549, -1.65952281e+1, -5.58478539], + [-7.41816999, 1.54645465e+1, 7.98777508], + [-8.42681552, 4.69511699, -1.65901139e+1], + [-9.78324506, -1.39304328e+1, 9.43216455], + [1.94440370e+1, 2.31090199, 2.43252766], + [3.15058476, -9.05446229, 1.75544337e+1], + [1.06459182e+1, 1.47218781e+1, -8.99355553], + [1.22190707e+1, -1.64529457e+1, 1.40712921], + [-1.90636227e+1, 2.16964624, 8.06436472], + [-2.34494082, 1.15135585e+1, 1.75050371e+1], + [1.78508769e+1, -2.19686564, -1.15098756e+1], + [2.31504343e-1, -1.38773263e+1, -1.65813076e+1], + [1.50443571e+1, 1.49229413e+1, 5.50536850], + [-1.79414521e+1, -6.05704638e-1, -1.29993027e+1], + [-1.90893740e+1, -1.14789797e+1, -2.66981417], + [-1.09824863e+1, -4.56662192, 1.93398756e+1], + [1.27613899e+1, -2.31578801, 1.89639329e+1], + [1.06885845, -2.27641276e+1, 4.58167139], + [-1.78853615e+1, 1.41595978e+1, 5.70950161], + [2.03291895e+1, -8.39726474, 9.05394871], + [1.93958349, 7.21737732, -2.28657560e+1], + [1.90522968, 2.16902183e+1, 1.08479166e+1], + [1.45272901e+1, -1.63102458e+1, -1.13106168e+1], + [-1.87965003e+1, 1.38830549e+1, -8.50402100], + [-8.67486188, 2.35641803e+1, -1.16532740], + [-2.08371573e+1, -9.29064237, 1.11821538e+1], + [-7.40930680, -5.51574155, -2.39589341e+1], + [1.55046343e+1, 8.72083349, -1.88907194e+1], + [9.62045596, 1.31513890e+1, 2.05400575e+1], + [8.89447741e-1, 2.16932725e+1, -1.51744310e+1], + [1.20103755e+1, -1.90498725e+1, 1.44533139e+1], + [2.47165331, -2.52776137e+1, -9.24753127], + [2.43672486e+1, 1.09659183e+1, -5.59054774], + [1.05756254e+1, 2.54424857e+1, -9.67114390e-1], + [-1.36887388e+1, -2.40892890e+1, 2.72043969], + [-2.80062395e+1, 8.56852541e-1, -2.26177776], + [-5.19799304, -1.81696491e+1, 2.11734080e+1], + [2.40714873e+1, 6.51484929, 1.41068791e+1], + [1.09672511e+1, -8.86272444, -2.52510743e+1], + [-1.66896882e+1, -1.74937806e+1, -1.63577116e+1], + [2.72013507e+1, -1.05512055e+1, -4.09451211], + [-1.68644649e+1, 1.27734462e+1, 2.08910298e+1], + [-1.36658517e+1, 1.36662584e+1, -2.29484206e+1]]) \ + if n_points == 111 else ( + np.array([[-0.75900911, 3.40732601, -6.63879075], + [10.95019756, -9.9362743, -2.53014984], + [-17.32322703, -10.12101153, -10.18854763], + [13.282736, 14.93163678, -22.37782999]]) + if n_points == 4 else make_interesting_starting_points( + n_points, tests)) + + lorenz: Final[System] = System( + "lorenz", 3, 1, 3, 3, 0.1, tests, training, 5000, 50.0, 5000, 50.0, + (0, 1, 2, len(training) + len(tests) - 1)) + lorenz.equations = __lorenz_equations # type: ignore + return lorenz
+ + + +#: The Lorenz system with 111 training points. +LORENZ_111: Final[System] = make_lorenz(111) + +#: The Lorenz system with 4 training points. +LORENZ_4: Final[System] = make_lorenz(4) +
diff --git a/_modules/moptipyapps/dynamic_control/systems/stuart_landau.html b/_modules/moptipyapps/dynamic_control/systems/stuart_landau.html new file mode 100644 index 00000000..f35a8d25 --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/systems/stuart_landau.html @@ -0,0 +1,196 @@ +moptipyapps.dynamic_control.systems.stuart_landau — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.systems.stuart_landau

+"""
+The two-dimensional Stuart-Landau system.
+
+The initial starting point of the work here were conversations with
+Prof. Dr. Bernd NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute
+of Technology in Shenzhen, China (哈尔滨工业大学(深圳)) as well as the
+following two MSc theses and book:
+
+1. Yuxiang LI (李宇翔). Jet Mixing Enhancement using Deep Reinforcement
+   Learning (基于深度强化学习的射流混合增强控制). MSc Thesis. Harbin Institute
+   of Technology in Shenzhen, China (哈尔滨工业大学(深圳)).
+   January 2023.
+2. Wei SUN (孙伟). Wake Control of 1-2-3 Fluidic Pinball using Deep
+   Reinforcement Learning (基于深度强化学习方法的 1-2-3 流体弹球尾流控制).
+   MSc Thesis. Harbin Institute of Technology in Shenzhen, China
+   (哈尔滨工业大学(深圳)). January 2023.
+3. Guy Yoslan CORNEJO MACEDA, François LUSSEYRAN, and Bernd R. NOACK.
+   xMLC: A Toolkit for Machine Learning Control, First Edition.
+   Machine Learning Tools in Fluid Mechanics, Vol 2.
+   Shenzhen & Paris; Universitätsbibliothek der Technischen Universität
+   Braunschweig. 2022 https://doi.org/10.24355/dbbs.084-202208220937-0
+"""
+
+from math import sqrt
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+from pycommons.types import check_int_range
+
+from moptipyapps.dynamic_control.starting_points import (
+    make_interesting_starting_points,
+)
+from moptipyapps.dynamic_control.system import System
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __stuart_landau_equations(state: np.ndarray, _: float,
+                              control: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute the differential equations of a controlled 2D Stuart-Landau model.
+
+    :param state: the state of the system
+    :param _: the time index, which is ignored
+    :param control: the output of the controller
+    :param out: the differential, i.e., the output of this function
+    """
+    sigma: Final[float] = 0.1 - state[0]**2 - state[1]**2
+    out[0] = sigma * state[0] - state[1]
+    out[1] = sigma * state[1] + state[0] + control[0]
+
+
+
+[docs] +def make_stuart_landau(n_points: int) -> System: + """ + Create the Stuart-Landau system. + + :param n_points: the number of training points + :return: the Stuart-Landau system + """ + tests: Final[np.ndarray] = np.array([[0.1, 0.0], [sqrt(0.1), 0.0]], float) + check_int_range(n_points, "n_points", 1, 1_000) + training: Final[np.ndarray] = np.array( + [[-0.0038209, -0.00240577], + [0.00307569, 0.00849048], + [0.00976623, -0.00938637], + [-0.01182026, 0.01365555], + [0.01777127, 0.01392328], + [0.0006177, -0.02708416], + [-0.03153332, -0.00214806], + [0.02792395, -0.02291339], + [-0.02847394, 0.02899283], + [0.01350566, 0.04308481], + [-0.02991208, -0.0396497], + [0.0540025, 0.00441161], + [-0.05551053, -0.01907852], + [-0.01080416, -0.06228265], + [-0.03100233, 0.06021576], + [-0.06858837, 0.02268735], + [0.04134416, 0.06467235], + [0.02084729, -0.07855437], + [0.07834551, -0.03495282], + [0.08049101, 0.04093911], + [-0.08926819, -0.03196673], + [-0.02208376, 0.0968485], + [0.07379551, -0.07306821], + [-0.08376945, 0.06874307], + [0.0275525, 0.10946577], + [-0.05169461, -0.10540068], + [-0.11942003, 0.02451537], + [-0.09709783, -0.08096571], + [0.10400721, 0.0795487], + [0.0636276, -0.11958202], + [-0.13503465, -0.03684538], + [-0.06982037, 0.12649679], + [0.07510267, 0.12868982], + [-0.03055115, -0.15044613], + [0.00066117, 0.15803063], + [0.15246865, -0.05634631], + [0.0232859, -0.1654316], + [0.17156619, 0.00197981], + [-0.16413595, 0.06378141], + [0.16890575, 0.06395391], + [0.14766927, -0.11164403], + [-0.08647114, -0.16877639], + [-0.12327999, 0.14999224], + [-0.19863449, -0.00369282], + [0.11207882, -0.1694759], + [-0.06793134, 0.19627607], + [0.16954787, 0.12762632], + [-0.14915337, -0.15724184], + [0.04633135, -0.21633926], + [0.06125791, 0.21729025], + [-0.18584921, 0.13596598], + [-0.1999492, -0.12307257], + [0.23850607, -0.01954563], + [0.1439136, 0.19681835], + [-0.02521609, -0.24705248], + [-0.00891478, 0.25269401], + [-0.24709608, -0.07197915], + [-0.10863885, -0.23828467], + [0.25404676, 0.08017174], + [0.1665829, -0.2136433], + [0.25571773, -0.10231616], + [-0.27547718, 0.04980043], + [-0.09155915, 0.26931962], + [0.0781761, -0.27819739], + [-0.26285954, 0.13053765], + [0.23770086, -0.17973375], + [0.12078667, 0.27735893], + [-0.30500318, -0.03525198], + [0.20619554, 0.23355099], + [-0.19712815, 0.24705659], + [-0.18688532, -0.26047056], + [0.26924503, 0.18219083], + [0.04358426, 0.32671534], + [-0.29933575, -0.14845035], + [-0.10118493, -0.32316973], + [0.15974011, -0.30370809], + [0.33188974, -0.10355639], + [0.0401967, -0.34988418], + [-0.14267712, 0.32692311], + [-0.27812549, -0.23048476], + [-0.3127193, 0.18964695], + [-0.36944776, 0.02430573], + [-0.03954588, 0.37266929], + [0.34413746, 0.15943751], + [-0.27511481, 0.26759701], + [0.14876924, 0.35867843], + [0.38719748, 0.06623872], + [0.25924229, -0.30111563], + [-0.03399581, -0.40041226], + [-0.38191229, -0.13884515], + [-0.38558806, 0.14193967], + [0.25389894, 0.32877223], + [-0.27063978, -0.32106318], + [0.36693718, -0.21330011], + [-0.23322584, 0.35999818], + [0.0372525, 0.43185548], + [-0.11945503, 0.42136931], + [0.0834364, -0.434552], + [0.44623645, -0.02619827], + [-0.44718354, -0.06242773], + [-0.2100912, -0.40475896], + [0.35704647, 0.29090293], + [0.20490764, -0.4174912], + [-0.46428805, 0.07030482], + [-0.39615467, -0.26043909], + [-0.10245864, -0.46751571], + [0.35093411, -0.33204879], + [0.45711698, 0.16981881], + [0.17358843, 0.46052731], + [-0.39398933, 0.30241613], + [0.47669529, -0.15475867]]) if n_points == 111 else ( + np.array([[-0.11744545, 0.04365611], + [-0.05513875, -0.2444522], + [-0.13321976, 0.35149126], + [-0.47720217, -0.15318856]]) if n_points == 4 else + make_interesting_starting_points(n_points, tests)) + + system: Final[System] = System( + "stuart_landau", 2, 1, 2, 2, 0.1, + tests, training, 5000, 50.0, 5000, 50.0, (0, 1)) + system.equations = __stuart_landau_equations # type: ignore + return system
+ + + +#: The Stuart-Landau system with 111 training points. +STUART_LANDAU_111: Final[System] = make_stuart_landau(111) + +#: The Stuart-Landau system with 4 training points. +STUART_LANDAU_4: Final[System] = make_stuart_landau(4) +
diff --git a/_modules/moptipyapps/dynamic_control/systems/three_coupled_oscillators.html b/_modules/moptipyapps/dynamic_control/systems/three_coupled_oscillators.html new file mode 100644 index 00000000..69999d9f --- /dev/null +++ b/_modules/moptipyapps/dynamic_control/systems/three_coupled_oscillators.html @@ -0,0 +1,157 @@ +moptipyapps.dynamic_control.systems.three_coupled_oscillators — moptipyapps 0.8.62 documentation

Source code for moptipyapps.dynamic_control.systems.three_coupled_oscillators

+"""
+A system of three coupled oscillators.
+
+There are three oscillators located in a two-dimensional plane. Thus, there
+are a total of six coordinates.
+
+The initial starting point of the work here were conversations with
+Prof. Dr. Bernd NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute
+of Technology in Shenzhen, China (哈尔滨工业大学(深圳)) as well as the
+following paper:
+
+1. Ruiying Li, Bernd R. Noack, Laurent Cordier, Jacques Borée, Eurika Kaiser,
+   and Fabien Harambat. Linear genetic programming control for strongly
+   nonlinear dynamics with frequency crosstalk. *Archives of Mechanics.*
+   70(6):505-534. Warszawa 2018. Seventy Years of the Archives of Mechanics.
+   https://doi.org/10.24423/aom.3000. Also: arXiv:1705.00367v1
+   [physics.flu-dyn] 30 Apr 2017. https://arxiv.org/abs/1705.00367.
+"""
+
+from math import pi
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+from pycommons.io.path import Path
+from pycommons.types import check_int_range
+
+from moptipyapps.dynamic_control.starting_points import (
+    make_interesting_starting_points,
+)
+from moptipyapps.dynamic_control.system import System
+
+#: pi to the square
+__PI2: Final[float] = pi * pi
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def __3_coupled_oscillators(state: np.ndarray, _: float,
+                            control: np.ndarray, out: np.ndarray) -> None:
+    """
+    Compute the differential equations of a controlled 3-oscillators system.
+
+    The system is six-dimensional. There are three oscillators, and each one
+    is updated. There is one control value.
+    This function implements equation (3.1) of the paper.
+
+    :param state: the state of the system
+    :param _: the time index, which is ignored
+    :param control: the output of the controller
+    :param out: the differential, i.e., the output of this function
+    """
+    a1: Final[float] = state[0]
+    a2: Final[float] = state[1]
+    a3: Final[float] = state[2]
+    a4: Final[float] = state[3]
+    a5: Final[float] = state[4]
+    a6: Final[float] = state[5]
+
+    r1sqr: Final[float] = (a1 * a1) + (a2 * a2)
+    r2sqr: Final[float] = (a3 * a3) + (a4 * a4)
+    r3sqr: Final[float] = (a5 * a5) + (a6 * a6)
+    sigma1: Final[float] = -r1sqr + r2sqr - r3sqr
+    sigma2: Final[float] = 0.1 - r2sqr
+    sigma3: Final[float] = -0.1
+
+    b: Final[float] = control[0]
+
+    # compute the differential system
+    out[0] = (sigma1 * a1) - a2
+    out[1] = (sigma1 * a2) + a1
+    out[2] = (sigma2 * a3) - (pi * a4)
+    out[3] = (sigma2 * a4) + (pi * a3) + b
+    out[4] = (sigma3 * a5) - (__PI2 * a6)
+    out[5] = (sigma3 * a6) + (__PI2 * a5) + b
+
+
+def _sinus_control(
+        _: np.ndarray, time: float, __: float, out: np.ndarray) -> None:
+    """
+    Present the sinus-based control law from the paper.
+
+    :param _: the state, ignored
+    :param time: the time
+    :param __: ignored
+    :param out: the output destination
+    """
+    out[0] = 0.07 * np.sin(__PI2 * time)
+
+
+def _lgpc3_36(
+        state: np.ndarray, time: float, _: float, out: np.ndarray) -> None:
+    """
+    Present the LGPC-3 equation 3.6.
+
+    :param state: the state
+    :param time: the time
+    :param _: ignored
+    :param out: the output destination
+    """
+    out[0] = np.tanh(np.sin(np.tanh(
+        3.0 * state[1] * np.sin(time) * np.sin(__PI2 * time) - state[3])))
+
+
+class __TO(System):
+    """The internal three oscillators class."""
+
+    def describe_system_without_control(
+            self, dest_dir: str, skip_if_exists: bool = True) \
+            -> tuple[Path, ...]:
+        result = list(super().describe_system_without_control(
+            dest_dir, skip_if_exists))
+        result.extend(self.describe_system(
+            "open loop 0.07sin(\u03C0²t)", _sinus_control, 0.0,
+            f"{self.name}_open_loop", dest_dir, skip_if_exists))
+        result.extend(self.describe_system(
+            "LGPC-3", _lgpc3_36, 0.0,
+            f"{self.name}_lgpc3", dest_dir, skip_if_exists))
+        return tuple(result)
+
+
+
+[docs] +def make_3_couple_oscillators(n_points: int) -> System: + """ + Create the oscillator system. + + :param n_points: the number of training points + :return: the Lorenz system + """ + check_int_range(n_points, "n_points", 1, 1_000) + tests: Final[np.ndarray] = \ + np.array([[0.1, 0, 0.1, 0, 0.1, 0]], float) + # similar to training = make_interesting_starting_points(111, tests) + training: Final[np.ndarray] = np.array([[ + -0.00721145, -0.00399194, -0.01078522, 0.0795534, 0.02929588, + 0.01588136], [ + 0.13202113, 0.05538969, -0.01011213, -0.06584996, 0.02154825, + -0.07136868], [ + 0.18476107, 0.10267522, -0.05342748, 0.12922333, -0.06481063, + -0.0133719], [ + -0.20572899, 0.08377647, 0.21218986, 0.00109397, 0.16296447, + 0.03239565]]) if n_points == 4 else ( + make_interesting_starting_points(n_points, tests)) + + three: Final[System] = __TO( + "3oscillators", 6, 1, 2, 2, 1.0, + tests, training, 5000, 50.0, 5000, 50.0, + (0, 1, 2, len(training) + len(tests) - 1)) + three.equations = __3_coupled_oscillators # type: ignore + return three
+ + + +#: The 3 oscillators system with 4 training points. +THREE_COUPLED_OSCILLATORS: Final[System] = make_3_couple_oscillators(4) +
diff --git a/_modules/moptipyapps/order1d/.nojekyll b/_modules/moptipyapps/order1d/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/moptipyapps/order1d/distances.html b/_modules/moptipyapps/order1d/distances.html new file mode 100644 index 00000000..19d96677 --- /dev/null +++ b/_modules/moptipyapps/order1d/distances.html @@ -0,0 +1,70 @@ +moptipyapps.order1d.distances — moptipyapps 0.8.62 documentation

Source code for moptipyapps.order1d.distances

+"""Some examples for distance metrics."""
+
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.utils.nputils import DEFAULT_BOOL
+
+
+
+[docs] +@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) +def swap_distance(p1: np.ndarray, p2: np.ndarray) -> int: + """ + Compute the swap distance between two permutations `p1` and `p1`. + + This is the minimum number of swaps required to translate `p1` to `p2` and + vice versa. This function is symmatric. + + An upper bound for the number of maximum number of swaps that could be + required is the length of the permutation. This upper bound can be derived + from Selection Sort. Imagine that I want to translate the array `p1` to + `p2`. I go through `p1` from beginning to end. If, at index `i`, I find + the right element (`p1[i] == p2[i]`), then I do nothing. If not, then the + right element must come at some index `j>i` (because all elements before I + already have fixed). So I swap `p1[i]` with `p1[j]`. Now `p1[i] == p2[i]` + and I increment `i`. Once I arrive at the end of `p1`, it must hold that + `all(p1[i] == p2[i])`. At the same time, I have performed at most one swap + at each index during the iteration. Hence, I can never need more swaps + than the arrays are long. + + :param p1: the first permutation + :param p2: the second permutation + :return: the swap distance, always between `0` and `len(p1)` + + >>> swap_distance(np.array([0, 1, 2, 3]), np.array([3, 1, 2, 0])) + 1 + >>> swap_distance(np.array([0, 1, 2]), np.array([0, 1, 2])) + 0 + >>> swap_distance(np.array([1, 0, 2]), np.array([0, 1, 2])) + 1 + >>> swap_distance(np.array([0, 1, 2]), np.array([1, 0, 2])) + 1 + >>> swap_distance(np.array([0, 1, 2]), np.array([2, 0, 1])) + 2 + >>> swap_distance(np.array([2, 0, 1]), np.array([0, 1, 2])) + 2 + >>> swap_distance(np.arange(10), np.array([4, 8, 1, 5, 9, 3, 6, 0, 7, 2])) + 7 + >>> swap_distance(np.array([4, 8, 1, 5, 9, 3, 6, 0, 7, 2]), np.arange(10)) + 7 + """ + n: Final[int] = len(p1) + x: np.ndarray = p2[np.argsort(p1)] + unchecked: np.ndarray = np.ones(n, DEFAULT_BOOL) + result: int = 0 + + for i in range(n): + if unchecked[i]: + result += 1 + unchecked[i] = False + j = x[i] + while j != i: + unchecked[j] = False + j = x[j] + + return n - result
+ +
diff --git a/_modules/moptipyapps/order1d/instance.html b/_modules/moptipyapps/order1d/instance.html new file mode 100644 index 00000000..e92f391f --- /dev/null +++ b/_modules/moptipyapps/order1d/instance.html @@ -0,0 +1,357 @@ +moptipyapps.order1d.instance — moptipyapps 0.8.62 documentation

Source code for moptipyapps.order1d.instance

+"""An instance of the ordering problem."""
+
+
+from math import isfinite
+from typing import Callable, Final, Iterable, TypeVar, cast
+
+import numpy as np
+from moptipy.utils.logger import KeyValueLogSection
+from moptipy.utils.nputils import DEFAULT_INT
+from moptipy.utils.strings import (
+    num_to_str_for_name,
+    sanitize_name,
+)
+from pycommons.types import check_int_range, type_error
+from scipy.stats import rankdata  # type: ignore
+
+from moptipyapps.qap.instance import Instance as QAPInstance
+
+#: the type variable for the source object
+T = TypeVar("T")
+
+#: the zero-based index column name
+_ZERO_BASED_INDEX: Final[str] = "indexZeroBased"
+#: the suggested x-coordinate
+_SUGGESTED_X_IN_0_1: Final[str] = "suggestedXin01"
+
+
+
+[docs] +class Instance(QAPInstance): + """ + An instance of the One-Dimensional Ordering Problem. + + Such an instance represents the ranking of objects by their distance to + each other as a :mod:`~moptipyapps.qap` problem. + + >>> def _dist(a, b): + ... return abs(a - b) + >>> def _tags(a): + ... return f"t{a}" + >>> the_instance = Instance.from_sequence_and_distance( + ... [1, 2, 3], _dist, 1, 100, ("bla", ), _tags) + >>> print(the_instance) + order1d_3_1 + >>> print(the_instance.distances) + [[0 1 2] + [1 0 1] + [2 1 0]] + >>> print(the_instance.flows) + [[0 4 2] + [3 0 3] + [2 4 0]] + """ + + def __init__(self, distances: np.ndarray, + flow_power: int | float, + horizon: int, + tag_titles: Iterable[str], + tags: Iterable[tuple[Iterable[str] | str, int]], + name: str | None = None) -> None: + """ + Create an instance of the quadratic assignment problem. + + :param distances: the original distance matrix + :param flow_power: the power to be used for constructing the flows + based on the original distances + :param horizon: the horizon for distance ranks, after which all + elements are ignored + :param tag_titles: the tag titles + :param tags: the assignment of rows to names + :param name: the instance name + """ + if not isinstance(flow_power, int | float): + raise type_error(flow_power, "flow_power", (int, float)) + if not (isfinite(flow_power) and (0 < flow_power < 100)): + raise ValueError( + f"flow_power must be in (0, 100), but is {flow_power}.") + + n: Final[int] = len(distances) # the number of elements + horizon = check_int_range(horizon, "horizon", 1, 1_000_000_000_000) + + # construct the distance matrix + dist_matrix = np.zeros((n, n), DEFAULT_INT) + for i in range(n): + for j in range(i + 1, n): + dist_matrix[i, j] = dist_matrix[j, i] = j - i + + # construct the flow matrix + flows: np.ndarray = rankdata( + distances, axis=1, method="average") - 1.0 + + # get the flow rank multiplier + multiplier: float = 1.0 + for i in range(n): + for j in range(n): + if i == j: + continue + f = flows[i, j] + if int(f) < f <= horizon: # dow we need the multiplier? + multiplier = 0.5 ** (-flow_power) + break + + flow_matrix = np.zeros((n, n), DEFAULT_INT) + max_val: Final[int] = min(n - 1, horizon) + for i in range(n): + for j in range(n): + if i == j: + continue + f = flows[i, j] + if f > horizon: + continue + flow_matrix[i, j] = int(round( + multiplier * ((max_val - f + 1) ** flow_power))) + + # construct a name if necessary + if name is None: + name = f"order1d_{n}_{num_to_str_for_name(flow_power)}" + if max_val < (n - 1): + name = f"{name}_{max_val}" + + super().__init__(dist_matrix, flow_matrix, name=name) + + if self.n != n: + raise ValueError("error when assigning number of elements!") + + #: the flow power + self.flow_power: Final[int | float] = flow_power + #: the horizon + self.horizon: Final[int] = max_val + + if not isinstance(tags, Iterable): + raise type_error(tags, "tags", Iterable) + if not isinstance(tag_titles, Iterable): + raise type_error(tag_titles, "tag_titles", Iterable) + + #: the tag titles + self.tag_titles: Final[tuple[str, ...]] = tuple(map( + sanitize_name, tag_titles)) + req_len: Final[int] = len(self.tag_titles) + if req_len <= 0: + raise ValueError("No tags specified.") + + #: the tags, i.e., additional data for each original element + self.tags: Final[tuple[tuple[tuple[str, ...], int], ...]] = ( + tuple(((t, ) if isinstance(t, str) + else tuple(t), k) for t, k in tags)) + if _ZERO_BASED_INDEX in self.tags: + raise ValueError(f"{_ZERO_BASED_INDEX} not permitted in tags.") + if _SUGGESTED_X_IN_0_1 in self.tags: + raise ValueError(f"{_SUGGESTED_X_IN_0_1} not permitted in tags.") + + if len(self.tags) < n: + raise ValueError(f"there must be at least {self.n} tags, but got " + f"{len(self.tags)}, i.e., {self.tags}") + for tag in self.tags: + check_int_range(tag[1], "id", 0, n) + if len(tag[0]) != req_len: + raise ValueError( + f"all tags must have the same length. " + f"while the first tag ({self.tags[0]}) has " + f"length {req_len}, {tag} has length {len(tag[0])}.") + +
+[docs] + @staticmethod + def from_sequence_and_distance( + data: Iterable[T | None], + get_distance: Callable[[T, T], int | float], + flow_power: int | float, + horizon: int, + tag_titles: Iterable[str], + get_tags: Callable[[T], str | Iterable[str]], + name: str | None = None) -> "Instance": + """ + Turn a sequence of objects into a One-Dimensional Ordering instance. + + :param data: the data source, i.e., an iterable of data elements + :param get_tags: the function for extracting tags from objects + :param get_distance: the function for getting the distance between + objects + :param flow_power: the flow power + :param horizon: the maximal considered rank + :param tag_titles: the tag names + :param name: the optional name + :return: the ordering instance + + >>> def _dist(a, b): + ... return abs(a - b) + >>> def _tags(a): + ... return f"x{a}", f"b{a}" + >>> res = Instance.from_sequence_and_distance( + ... [1, 2, 5], _dist, 2, 100, ("a", "b"), _tags) + >>> print(res) + order1d_3_2 + >>> print(res.flows) + [[0 4 1] + [4 0 1] + [1 4 0]] + >>> print(res.distances) + [[0 1 2] + [1 0 1] + [2 1 0]] + >>> res = Instance.from_sequence_and_distance( + ... [1, 2, 35, 4], _dist, 2, 100, ("a", "b"), _tags) + >>> print(res) + order1d_4_2 + >>> print(res.flows) + [[0 9 1 4] + [9 0 1 4] + [1 4 0 9] + [4 9 1 0]] + >>> print(res.distances) + [[0 1 2 3] + [1 0 1 2] + [2 1 0 1] + [3 2 1 0]] + >>> res = Instance.from_sequence_and_distance( + ... [1, 2, 4, 4], _dist, 2, 100, ("a", "b"), _tags) + >>> print(res) + order1d_3_2 + >>> print(res.flows) + [[0 4 1] + [4 0 1] + [1 4 0]] + >>> print(res.distances) + [[0 1 2] + [1 0 1] + [2 1 0]] + >>> print(res.tags) + ((('x1', 'b1'), 0), (('x2', 'b2'), 1), (('x4', 'b4'), 2), \ +(('x4', 'b4'), 2)) + >>> def _dist2(a, b): + ... return abs(abs(a) - abs(b)) + 1 + >>> res = Instance.from_sequence_and_distance( + ... [1, 2, -4, 4], _dist2, 2, 100, ("a", "b"), _tags) + >>> print(res) + order1d_4_2 + >>> print(res.flows) + [[ 0 36 9 9] + [36 0 9 9] + [ 4 16 0 36] + [ 4 16 36 0]] + >>> print(res.distances) + [[0 1 2 3] + [1 0 1 2] + [2 1 0 1] + [3 2 1 0]] + >>> res = Instance.from_sequence_and_distance( + ... [1, 2, -4, 4], _dist2, 3, 100, ("a", "b"), _tags) + >>> print(res) + order1d_4_3 + >>> print(res.flows) + [[ 0 216 27 27] + [216 0 27 27] + [ 8 64 0 216] + [ 8 64 216 0]] + >>> print(res.distances) + [[0 1 2 3] + [1 0 1 2] + [2 1 0 1] + [3 2 1 0]] + >>> print(res.tags) + ((('x1', 'b1'), 0), (('x2', 'b2'), 1), (('x-4', 'b-4'), 2), \ +(('x4', 'b4'), 3)) + >>> res = Instance.from_sequence_and_distance( + ... [1, 2, -4, 4], _dist2, 3, 2, ("a", "b"), _tags) + >>> print(res) + order1d_4_3_2 + >>> print(res.flows) + [[0 8 0 0] + [8 0 0 0] + [0 1 0 8] + [0 1 8 0]] + >>> print(res.distances) + [[0 1 2 3] + [1 0 1 2] + [2 1 0 1] + [3 2 1 0]] + >>> print(res.tags) + ((('x1', 'b1'), 0), (('x2', 'b2'), 1), (('x-4', 'b-4'), 2), \ +(('x4', 'b4'), 3)) + >>> res = Instance.from_sequence_and_distance( + ... [1, 2, -4, 4], _dist2, 2, 2, ("a", "b"), _tags) + >>> print(res) + order1d_4_2_2 + >>> print(res.flows) + [[0 4 0 0] + [4 0 0 0] + [0 1 0 4] + [0 1 4 0]] + >>> print(res.distances) + [[0 1 2 3] + [1 0 1 2] + [2 1 0 1] + [3 2 1 0]] + """ + if not isinstance(data, Iterable): + raise type_error(data, "data", Iterable) + if not callable(get_tags): + raise type_error(get_tags, "get_tags", call=True) + if not callable(get_distance): + raise type_error(get_distance, "get_distance", call=True) + if not isinstance(tag_titles, Iterable): + raise type_error(tag_titles, "tag_titles", Iterable) + + # build a distance matrix and purge all zero-distance elements + datal: list[T] = cast(list[T], data) \ + if isinstance(data, list) else list(data) + mappings: list[tuple[T, int]] = [] + distances: list[list[int | float]] = [] + i: int = 0 + while i < len(datal): + o1: T = datal[i] + j: int = i + 1 + current_dists: list[int | float] = [d[i] for d in distances] + current_dists.append(0) + while j < len(datal): + o2: T = datal[j] + dist: int | float = get_distance(o1, o2) + if not (isfinite(dist) and (0 <= dist <= 1e100)): + raise ValueError( + f"invalid distance {dist} for objects {i}, {j}") + if dist <= 0: # distance == 0, must purge + mappings.append((o2, i)) + del datal[j] + for ds in distances: + del ds[j] + continue + current_dists.append(dist) + j += 1 + mappings.append((o1, i)) + distances.append(current_dists) + i += 1 + + # we now got a full distance matrix, let's turn it into a rank matrix + return Instance(np.array(distances), + flow_power, horizon, tag_titles, + ((get_tags(obj), idx) for obj, idx in mappings), + name)
+ + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log all parameters of this component as key-value pairs. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + logger.key_value("flowPower", self.flow_power) + logger.key_value("horizon", self.horizon) + logger.key_value("nOrig", len(self.tags))
+
+ +
diff --git a/_modules/moptipyapps/order1d/space.html b/_modules/moptipyapps/order1d/space.html new file mode 100644 index 00000000..9352681c --- /dev/null +++ b/_modules/moptipyapps/order1d/space.html @@ -0,0 +1,102 @@ +moptipyapps.order1d.space — moptipyapps 0.8.62 documentation

Source code for moptipyapps.order1d.space

+"""
+An extension of the permutation space for one-dimensional ordering.
+
+The main difference is how the `to_str` result is noted, namely that it
+contains the mapping of tags to locations.
+
+>>> def _dist(a, b):
+...     return abs(a - b)
+>>> def _tags(a):
+...     return f"t{a}"
+>>> the_instance = Instance.from_sequence_and_distance(
+...     [1, 2, 3, 3, 2, 3], _dist, 2, 10, ("x", ), _tags)
+>>> the_space = OrderingSpace(the_instance)
+>>> the_str = the_space.to_str(np.array([0, 2, 1]))
+>>> the_str.splitlines()
+['0;2;1', '', 'indexZeroBased;suggestedXin01;x', '0;0;t1', '2;1;t2', \
+'2;1;t2', '1;0.5;t3', '1;0.5;t3', '1;0.5;t3']
+>>> print(the_space.from_str(the_str))
+[0 2 1]
+"""
+
+from typing import Final
+
+import numpy as np
+from moptipy.spaces.permutations import Permutations
+from moptipy.utils.logger import CSV_SEPARATOR
+from moptipy.utils.strings import float_to_str
+from pycommons.types import type_error
+
+from moptipyapps.order1d.instance import (
+    _SUGGESTED_X_IN_0_1,
+    _ZERO_BASED_INDEX,
+    Instance,
+)
+
+
+
+[docs] +class OrderingSpace(Permutations): + """A space for one-dimensional orderings.""" + + def __init__(self, instance: Instance) -> None: + """ + Create an ordering space from an instance. + + :param instance: the instance + """ + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + super().__init__(range(instance.n)) + #: the instance + self.instance: Final[Instance] = instance + +
+[docs] + def to_str(self, x: np.ndarray) -> str: + """ + Convert a solution to a string. + + :param x: the permutation + :return: the string + """ + tags: Final[tuple[tuple[tuple[str, ...], int], ...]] \ + = self.instance.tags + + n: Final[int] = len(self.blueprint) - 1 + text: list[str] = [] + + text.extend(super().to_str(x).split("\n")) + text.append("") # noqa: PIE799 + + row: list[str] = [_ZERO_BASED_INDEX, _SUGGESTED_X_IN_0_1] + row.extend(self.instance.tag_titles) + text.append(f"{CSV_SEPARATOR.join(row)}") + + for tag, i in tags: + row.clear() + row.append(str(x[i])) + row.append(float_to_str(x[i] / n)) + row.extend(tag) + text.append(f"{CSV_SEPARATOR.join(row)}") + return "\n".join(text)
+ + +
+[docs] + def from_str(self, text: str) -> np.ndarray: + """ + Get the string version from the given text. + + :param text: the text + :return: the string + """ + text = text.lstrip() + idx: int = text.find("\n") + if idx > 0: + text = text[:idx] + return super().from_str(text.rstrip())
+
+ +
diff --git a/_modules/moptipyapps/qap/.nojekyll b/_modules/moptipyapps/qap/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/moptipyapps/qap/instance.html b/_modules/moptipyapps/qap/instance.html new file mode 100644 index 00000000..47b7cb0f --- /dev/null +++ b/_modules/moptipyapps/qap/instance.html @@ -0,0 +1,500 @@ +moptipyapps.qap.instance — moptipyapps 0.8.62 documentation

Source code for moptipyapps.qap.instance

+"""
+An instance of the Quadratic Assignment Problem.
+
+In this module, we provide the class :class:`~Instance` that encapsulates all
+information of a problem instance of the Quadratic Assignment Problem (QAP).
+The QAP aims to locate facilities at locations such that the flow-distance
+product sum combining a flows of goods between instances with the distances of
+the locations becomes minimal. Each instance therefore presents a matrix with
+:attr:`~moptipyapps.qap.instance.Instance.distances` and a matrix with flows
+:attr:`~moptipyapps.qap.instance.Instance.flows`.
+
+1. Eliane Maria Loiola, Nair Maria Maia de Abreu, Paulo Oswaldo
+   Boaventura-Netto, Peter Hahn, and Tania Querido. A survey for the
+   Quadratic Assignment Problem. European Journal of Operational Research.
+   176(2):657-690. January 2007. https://doi.org/10.1016/j.ejor.2005.09.032.
+2. Rainer E. Burkard, Eranda Çela, Panos M. Pardalos, and
+   Leonidas S. Pitsoulis. The Quadratic Assignment Problem. In Ding-Zhu Du,
+   Panos M. Pardalos, eds., Handbook of Combinatorial Optimization,
+   pages 1713-1809, 1998, Springer New York, NY, USA.
+   https://doi.org/10.1007/978-1-4613-0303-9_27.
+
+
+We additionally provide access to several standard QAP benchmark instances
+via the :meth:`~Instance.from_resource` and :meth:`~Instance.list_resources`
+methods. The standard benchmark instances stem from QAPLIB, a library of QAP
+instances, which can be found at <https://qaplib.mgi.polymtl.ca> and
+<https://coral.ise.lehigh.edu/data-sets/qaplib>.
+
+1. QAPLIB - A Quadratic Assignment Problem Library. The Websites
+   <https://qaplib.mgi.polymtl.ca/> (updated 2018) and
+   <https://coral.ise.lehigh.edu/data-sets/qaplib/> (updated 2011), including
+   the benchmark instances, on visited 2023-10-21.
+2. Rainer E. Burkard, Stefan E. Karisch, and Franz Rendl. QAPLIB - A Quadratic
+   Assignment Problem Library. Journal of Global Optimization. 10:391-403,
+   1997. https://doi.org/10.1023/A:1008293323270.
+"""
+
+
+from typing import Final, Iterable, cast
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.api.component import Component
+from moptipy.utils.logger import KeyValueLogSection
+from moptipy.utils.nputils import (
+    DEFAULT_UNSIGNED_INT,
+    int_range_to_dtype,
+    is_np_int,
+)
+from moptipy.utils.strings import sanitize_name
+from pycommons.types import check_int_range, check_to_int_range, type_error
+
+from moptipyapps.qap.qaplib import open_resource_stream
+
+
+
+[docs] +@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) +def trivial_bounds(distances: np.ndarray, flows: np.ndarray) \ + -> tuple[int, int]: + """ + Compute the trivial bounds for the QAP objective. + + A trivial upper bound is to multiply the largest flow with the largest + distance, the second-largest flow with the second-largest distance, the + third-largest flow with the third-largest distance, and so on. + + A trivial lower bound is to multiply the largest flow with the shortest + distance, the second-largest flow with the second-shortest distance, the + third-largest flow with the third-shortest distance, and so on. + + :param distances: the distance matrix + :param flows: the flow matrix + :return: the lower and upper bounds + + >>> dst = np.array([[0, 1, 2], [3, 0, 4], [5, 6, 0]]) + >>> flws = np.array([[0, 95, 86], [23, 0, 55], [24, 43, 0]]) + >>> 0*95 + 0*86 + 0*55 + 1*43 + 2*24 + 3*23 + 4*0 + 5*0 + 6*0 + 160 + >>> 6*95 + 5*86 + 4*55 + 3*43 + 2*24 + 1*23 + 0*0 + 0*0 + 0*0 + 1420 + >>> trivial_bounds(dst, flws) + (160, 1420) + """ + n: int = len(distances) + n *= n + df_ub: Final[np.ndarray] = np.empty(n, DEFAULT_UNSIGNED_INT) + df_ub[:] = distances.flatten() + df_ub.sort() + df_lb: Final[np.ndarray] = np.empty(n, DEFAULT_UNSIGNED_INT) + df_lb[:] = df_ub[::-1] + ff: Final[np.ndarray] = np.empty(n, DEFAULT_UNSIGNED_INT) + ff[:] = flows.flatten() + ff.sort() + return (int(np.multiply(df_lb, ff, df_lb).sum()), + int(np.multiply(df_ub, ff, df_ub).sum()))
+ + + +def _flow_or_dist_to_int(val: str) -> int: + """ + Convert a flow or distance string to an integer. + + :param val: the value + :return: the integer + """ + return check_to_int_range(val, "value", 0, 1_000_000_000_000_000) + + +#: the instances of the QAPLib library +_INSTANCES: Final[tuple[str, ...]] = ( + "bur26a", "bur26b", "bur26c", "bur26d", "bur26e", "bur26f", "bur26g", + "bur26h", "chr12a", "chr12b", "chr12c", "chr15a", "chr15b", "chr15c", + "chr18a", "chr18b", "chr20a", "chr20b", "chr20c", "chr22a", "chr22b", + "chr25a", "els19", "esc16a", "esc16b", "esc16c", "esc16d", "esc16e", + "esc16f", "esc16g", "esc16h", "esc16i", "esc16j", "esc32a", "esc32b", + "esc32c", "esc32d", "esc32e", "esc32g", "esc32h", "esc64a", "esc128", + "had12", "had14", "had16", "had18", "had20", "kra30a", "kra30b", "kra32", + "lipa20a", "lipa20b", "lipa30a", "lipa30b", "lipa40a", "lipa40b", + "lipa50a", "lipa50b", "lipa60a", "lipa60b", "lipa70a", "lipa70b", + "lipa80a", "lipa80b", "lipa90a", "lipa90b", "nug12", "nug14", "nug15", + "nug16a", "nug16b", "nug17", "nug18", "nug20", "nug21", "nug22", "nug24", + "nug25", "nug27", "nug28", "nug30", "rou12", "rou15", "rou20", "scr12", + "scr15", "scr20", "sko42", "sko49", "sko56", "sko64", "sko72", "sko81", + "sko90", "sko100a", "sko100b", "sko100c", "sko100d", "sko100e", "sko100f", + "ste36a", "ste36b", "ste36c", "tai12a", "tai12b", "tai15a", "tai15b", + "tai17a", "tai20a", "tai20b", "tai25a", "tai25b", "tai30a", "tai30b", + "tai35a", "tai35b", "tai40a", "tai40b", "tai50a", "tai50b", "tai60a", + "tai60b", "tai64c", "tai80a", "tai80b", "tai100a", "tai100b", "tai150b", + "tai256c", "tho30", "tho40", "tho150", "wil50", "wil100") + +#: the lower bounds provided at <https://qaplib.mgi.polymtl.ca/> +_BOUNDS: Final[dict[str, int]] = { + "nug14": 1014, "nug15": 1150, "nug16a": 1610, "nug16b": 1240, + "nug17": 1732, "nug18": 1930, "nug20": 2570, "nug21": 2438, "nug22": 3596, + "nug24": 3488, "nug25": 3744, "nug27": 5234, "nug28": 5166, "nug30": 6124, + "rou12": 235528, "rou15": 354210, "rou20": 725522, "scr12": 31410, + "scr15": 51140, "scr20": 110030, "sko100a": 143846, "sko100b": 145522, + "sko100c": 139881, "sko100d": 141289, "sko100e": 140893, "sko100f": 140691, + "sko42": 15332, "sko49": 22650, "sko56": 33385, "sko64": 47017, + "sko72": 64455, "sko81": 88359, "sko90": 112423, "ste36a": 9526, + "ste36b": 15852, "ste36c": 8239110, "tai100a": 17853840, + "tai100b": 1151591000, "tai12a": 224416, "tai12b": 39464925, + "tai150b": 441786736, "tai15a": 388214, "tai15b": 51765268, + "tai17a": 491812, "tai20a": 703482, "tai20b": 122455319, + "tai256c": 44095032, "tai25a": 1167256, "tai25b": 344355646, + "tai30a": 1706855, "tai30b": 637117113, "tai35a": 2216627, + "tai35b": 269532400, "tai40a": 2843274, "tai40b": 608808400, + "tai50a": 4390920, "tai50b": 431090700, "tai60a": 6325978, + "tai60b": 592371800, "tai64c": 1855928, "tai80a": 11657010, + "tai80b": 786298800, "tho150": 7620628, "tho30": 149936, "tho40": 226490, + "wil100": 268955, "wil50": 48121, "bur26a": 5426670, "bur26b": 3817852, + "bur26c": 5426795, "bur26d": 3821225, "bur26e": 5386879, "bur26f": 3782044, + "bur26g": 10117172, "bur26h": 7098658, "chr12a": 9552, "chr12b": 9742, + "chr12c": 11156, "chr15a": 9896, "chr15b": 7990, "chr15c": 9504, + "chr18a": 11098, "chr18b": 1534, "chr20a": 2192, "chr20b": 2298, + "chr20c": 14142, "chr22a": 6156, "chr22b": 6194, "chr25a": 3796, + "els19": 17212548, "esc128": 64, "esc16a": 68, "esc16b": 292, + "esc16c": 160, "esc16d": 16, "esc16e": 28, "esc16f": 0, "esc16g": 26, + "esc16h": 996, "esc16i": 14, "esc16j": 8, "esc32a": 130, "esc32b": 168, + "esc32c": 642, "esc32d": 200, "esc32e": 2, "esc32g": 6, "esc32h": 438, + "esc64a": 116, "had12": 1652, "had14": 2724, "had16": 3720, "had18": 5358, + "had20": 6922, "kra30a": 88900, "kra30b": 91420, "kra32": 88700, + "lipa20a": 3683, "lipa20b": 27076, "lipa30a": 13178, "lipa30b": 151426, + "lipa40a": 31538, "lipa40b": 476581, "lipa50a": 62093, "lipa50b": 1210244, + "lipa60a": 107218, "lipa60b": 2520135, "lipa70a": 169755, + "lipa70b": 4603200, "lipa80a": 253195, "lipa80b": 7763962, + "lipa90a": 360630, "lipa90b": 12490441, "nug12": 578} + +#: The best-known solutions of the QAPLIB instances that are not yet solved to +#: optimality, as of 2024-05-09 on https://qaplib.mgi.polymtl.ca/ +_BKS: Final[dict[str, tuple[str, int]]] = { + "sko90": ("T1991RTSFTQAP,T1995COISFTQAP", 115534), + "sko100a": ("FF1993GHFTQAP", 152002), + "sko100b": ("FF1993GHFTQAP", 153890), + "sko100c": ("FF1993GHFTQAP", 147862), + "sko100d": ("FF1993GHFTQAP", 149576), + "sko100e": ("FF1993GHFTQAP", 149150), + "sko100f": ("FF1993GHFTQAP", 149036), + "tai30a": ("T1991RTSFTQAP,T1995COISFTQAP", 1818146), + "tai35a": ("T1991RTSFTQAP,T1995COISFTQAP", 2422002), + "tai35b": ("T1991RTSFTQAP,T1995COISFTQAP", 283315445), + "tai40a": ("T1991RTSFTQAP,T1995COISFTQAP", 3139370), + "tai40b": ("T1991RTSFTQAP,T1995COISFTQAP", 637250948), + "tai50a": ("M2008AIOTITSAFTQAP", 4938796), + "tai50b": ("T1991RTSFTQAP,T1995COISFTQAP", 458821517), + "tai60a": ("M2005ATSAFTQAP", 7205962), + "tai60b": ("T1991RTSFTQAP,T1995COISFTQAP", 608215054), + "tai80a": ("M2008AIOTITSAFTQAP", 13499184), + "tai80b": ("T1991RTSFTQAP,T1995COISFTQAP", 818415043), + "tai100a": ("M2008AIOTITSAFTQAP", 21044752), + "tai100b": ("T1991RTSFTQAP,T1995COISFTQAP", 1185996137), + "tai150b": ("TG1997AMFTQAP", 498896643), + "tai256c": ("S1997MMASFQAP", 44759294), + "tho40": ("TB1994AISAAFTQAP", 240516), + "tho150": ("M2003AMSAAFTQAP", 8133398), + "wil100": ("FF1993GHFTQAP", 273038), +} + + +
+[docs] +class Instance(Component): + """An instance of the Quadratic Assignment Problem.""" + + def __init__(self, distances: np.ndarray, flows: np.ndarray, + lower_bound: int | None = None, + upper_bound: int | None = None, + name: str | None = None) -> None: + """ + Create an instance of the quadratic assignment problem. + + :param distances: the distance matrix + :param flows: the flow matrix + :param lower_bound: the optional lower bound + :param upper_bound: the optional upper bound + :param name: the name of this instance + """ + super().__init__() + if not isinstance(distances, np.ndarray): + raise type_error(distances, "distances", np.ndarray) + if not isinstance(flows, np.ndarray): + raise type_error(flows, "flows", np.ndarray) + shape: tuple[int, ...] = distances.shape + if len(shape) != 2: + raise ValueError("distance matrix must have two dimensions, but " + f"has {len(shape)}, namely {shape}.") + if shape[0] != shape[1]: + raise ValueError( + f"distance matrix must be square, but has shape {shape}.") + if not is_np_int(distances.dtype): + raise ValueError("distance matrix must be integer, but has " + f"dtype {distances.dtype}.") + if shape != flows.shape: + raise ValueError( + f"flow matrix has shape {flows.shape} and distance matrix has" + f" shape {shape}, which are different.") + if not is_np_int(flows.dtype): + raise ValueError( + f"flow matrix must be integer, but has dtype {flows.dtype}.") + + lb, ub = trivial_bounds(distances, flows) + if lower_bound is not None: + lb = max(lb, check_int_range( + lower_bound, "lower_bound", 0, 1_000_000_000_000_000)) + if upper_bound is not None: + ub = min(ub, check_int_range( + upper_bound, "upper_bound", 0, 1_000_000_000_000_000)) + if lb > ub: + raise ValueError(f"lower bound = {lb} > upper_bound = {ub}!") + dtype: Final[np.dtype] = int_range_to_dtype(min_value=0, max_value=ub) + #: the scale of the problem + self.n: Final[int] = shape[0] + if name is None: + name = f"qap{self.n}_{lb}_{ub}" + else: + sn: Final[str] = sanitize_name(name) + if name != sn: + raise ValueError(f"name={name!r} sanitizes to {sn!r}.") + #: the name of this instance + self.name: Final[str] = name + #: the distance matrix + self.distances: Final[np.ndarray] = \ + distances.astype(dtype) if distances.dtype != dtype else distances + #: the flows + self.flows: Final[np.ndarray] = \ + flows.astype(dtype) if flows.dtype != dtype else flows + #: the lower bound for the QAP objective + self.lower_bound: Final[int] = lb + #: the upper bound for the QAP objective + self.upper_bound: Final[int] = ub + + def __str__(self): + """ + Get the name of this instance. + + :return: :attr:`~name` + """ + return self.name + +
+[docs] + def bks(self) -> tuple[str, int]: + """ + Get the best-known solution, if known, the optimum, or lower bound. + + A tuple with a string identifying the source of the value and a value + corresponding to the best-known solution: + + - `("OPT", xxx)`: the problem instance has been solved to optimality + and `xxx` is the objective value of the optimum + - `("LB", xxx)`: neither the optimum nor a best-known solution are + available for this instance, so we return the lower bound + + The data is based on https://qaplib.mgi.polymtl.ca/, visited on + 2024-05-09. The following sources are included: + + - "FF1993GHFTQAP": Charles Fleurent and Jacques A. Ferland. Genetic + Hybrids for the Quadratic Assignment Problem. In Panos M. Pardalos + and Henry Wolkowicz, eds, *Quadratic Assignment and Related + Problems, Proceedings of a DIMACS Workshop,* May 20-21, 1993. + pages 173-187. Providence, RI, USA: American Mathematical Society. + - "M2003AMSAAFTQAP": Alfonsas Misevičius, A Modified Simulated + Annealing Algorithm for the Quadratic Assignment Problem. + *Informatica* 14(4):497-514. January 2003. + https://doi.org/10.15388/Informatica.2003.037. + - "M2005ATSAFTQAP": Alfonsas Misevičius. A Tabu Search Algorithm for + the Quadratic Assignment Problem. + *Computational Optimization and Applications* 30(1):95-111. 2005. + https://doi.org/10.1007/s10589-005-4562-x. + - "M2008AIOTITSAFTQAP": Alfonsas Misevičius. An Implementation of + the Iterated Tabu Search Algorithm for the Quadratic Assignment + Problem. Working Paper. 2008. Kaunas, Lithuania: Kaunas University + of Technology. + - "S1997MMASFQAP": Thomas Stützle. MAX-MIN Ant System for Quadratic + Assignment Problems. Research Report AIDA-97-04. 1997. Darmstadt, + Germany: Department of Computer Schience, Darmstadt University of + Technology. + - "T1991RTSFTQAP": Éric Taillard. Robust Taboo Search for the + Quadratic Assignment Problem. *Parallel Computing.* + 17(4-5):443-455. July 1991. + - "T1995COISFTQAP": Éric D. Taillard. Comparison of Iterative + Searches for the Quadratic Assignment Problem. *Location Science* + 3(2):87-105. 1995. + - "TB1994AISAAFTQAP": Ulrich Wilhelm Thonemann and Andreas Bölte. + An Improved Simulated Annealing Algorithm for the Quadratic + Assignment Problem. Working Paper 1994. Paderborn, Germany: School + of Business, Department of Production and Operations Research, + University of Paderborn. + - "TG1997AMFTQAP": Éric D. Taillard and Luca-Maria Gambardella. + Adaptive Memories for the Quadratic Assignment Problem. 1997. + Technical Report IDSIA-87-97. Lugano, Switzerland: IDSIA. + + :return: a `tuple[str, int]` with the objective value of the + best-known (if any is available), the optimum, or the lower bound. + """ + name: Final[str] = self.name + if name in _BKS: + return _BKS[name] + if name in _BOUNDS: + return "OPT", _BOUNDS[name] + return "LB", self.lower_bound
+ + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log all parameters of this instance as key-value pairs. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + logger.key_value("n", self.n) + logger.key_value("qapLowerBound", self.lower_bound) + logger.key_value("qapUpperBound", self.upper_bound)
+ + +
+[docs] + @staticmethod + def from_qaplib_stream(stream: Iterable[str], + lower_bound: int | None = None, + upper_bound: int | None = None, + name: str | None = None) -> "Instance": + """ + Load an instance from a QAPLib-formatted stream. + + :param stream: the stream to load the data from + :param lower_bound: the optional lower bound + :param upper_bound: the optional upper bound + :param name: the name of this instance + :return: the instance + + >>> ins = Instance.from_qaplib_stream([ + ... "4", "", + ... "1 2 3 4 5 6 7 8 9 10 11 12 13", + ... " 14 15 16 ", "", + ... "17 18 19 20 21 22 23 24 25 26 27", + ... " 28 29 30 31 32"]) + >>> ins.distances + array([[17, 18, 19, 20], + [21, 22, 23, 24], + [25, 26, 27, 28], + [29, 30, 31, 32]], dtype=int16) + >>> ins.flows + array([[ 1, 2, 3, 4], + [ 5, 6, 7, 8], + [ 9, 10, 11, 12], + [13, 14, 15, 16]], dtype=int16) + >>> ins.lower_bound + 2992 + >>> ins.upper_bound + 3672 + >>> ins.name + 'qap4_2992_3672' + """ + state: int = 0 + n: int | None = None + n2: int = -1 + flows: list[int] = [] + dists: list[int] = [] + for oline in stream: + line = oline.strip() + if len(line) <= 0: + continue + if state == 0: + n = check_to_int_range(line, "n", 1, 1_000_000) + n2 = n * n + state = 1 + else: + row: Iterable[int] = map(_flow_or_dist_to_int, line.split()) + if state == 1: + flows.extend(row) + if len(flows) >= n2: + state = 2 + continue + dists.extend(row) + if len(dists) >= n2: + state = 3 + break + + if (n is None) or (n <= 0): + raise ValueError(f"Invalid or unspecified size n={n}.") + lll: int = len(flows) + if lll != n2: + raise ValueError( + f"Invalid number of flows {lll}, should be n²={n}²={n2}.") + lll = len(dists) + if lll != n2: + raise ValueError( + f"Invalid number of distances {lll}, should be n²={n}²={n2}.") + if state != 3: + raise ValueError(f"Stream is incomplete, state={state}.") + + return Instance(np.array(dists, DEFAULT_UNSIGNED_INT).reshape((n, n)), + np.array(flows, DEFAULT_UNSIGNED_INT).reshape((n, n)), + lower_bound, upper_bound, name)
+ + +
+[docs] + @staticmethod + def list_resources() -> tuple[str, ...]: + """ + Get a tuple of all the QAP-lib instances available as resource. + + The original data can be found at <https://qaplib.mgi.polymtl.ca> and + <https://coral.ise.lehigh.edu/data-sets/qaplib>. + + :return: the tuple with the instance names + + >>> len(Instance.list_resources()) + 134 + """ + return _INSTANCES
+ + +
+[docs] + @staticmethod + def from_resource(name: str) -> "Instance": + """ + Load a QAP-Lib instance from a resource. + + The original data can be found at <https://qaplib.mgi.polymtl.ca> and + <https://coral.ise.lehigh.edu/data-sets/qaplib>. + + :param name: the name string + :return: the instance + + >>> insta = Instance.from_resource("chr25a") + >>> print(insta.n) + 25 + >>> print(insta.name) + chr25a + >>> print(insta.lower_bound) + 3796 + >>> print(insta.upper_bound) + 50474 + """ + if not isinstance(name, str): + raise type_error(name, "name", str) + container: Final = Instance.from_resource + inst_attr: Final[str] = f"__inst_{name}" + if hasattr(container, inst_attr): # instance loaded? + return cast(Instance, getattr(container, inst_attr)) + + lb: int | None = _BOUNDS.get(name, None) + with open_resource_stream(f"{name}.dat") as stream: + inst: Final[Instance] = Instance.from_qaplib_stream( + stream, name=name, lower_bound=lb) + + if inst.n <= 1000: + setattr(container, inst_attr, inst) + return inst
+
+ +
diff --git a/_modules/moptipyapps/qap/objective.html b/_modules/moptipyapps/qap/objective.html new file mode 100644 index 00000000..309caf49 --- /dev/null +++ b/_modules/moptipyapps/qap/objective.html @@ -0,0 +1,145 @@ +moptipyapps.qap.objective — moptipyapps 0.8.62 documentation

Source code for moptipyapps.qap.objective

+"""
+The objective function for the Quadratic Assignment Problem.
+
+The candidate solutions to the QAP are
+:mod:`~moptipy.spaces.permutations` `p` of length `n` of the numbers `0`, `1`,
+..., `n-1`. An :mod:`~moptipyapps.qap.instance` of the Quadratic Assignment
+Problem (QAP) presents a `n*n` matrix `D` with
+:attr:`~moptipyapps.qap.instance.Instance.distances` and a `n*n` matrix `F`
+with flows :attr:`~moptipyapps.qap.instance.Instance.flows`. The objective
+value, subject to minimization, is then the
+`sum( F[i,j] * D[p[i], p[j]] for i, j in 0..n-1 )`, i.e., the sum of the
+products of the flows between facilities and the distances of their assigned
+locations.
+
+1. Eliane Maria Loiola, Nair Maria Maia de Abreu, Paulo Oswaldo
+   Boaventura-Netto, Peter Hahn, and Tania Querido. A survey for the
+   Quadratic Assignment Problem. European Journal of Operational Research.
+   176(2):657-690. January 2007. https://doi.org/10.1016/j.ejor.2005.09.032.
+2. Rainer E. Burkard, Eranda Çela, Panos M. Pardalos, and
+   Leonidas S. Pitsoulis. The Quadratic Assignment Problem. In Ding-Zhu Du,
+   Panos M. Pardalos, eds., Handbook of Combinatorial Optimization,
+   pages 1713-1809, 1998, Springer New York, NY, USA.
+   https://doi.org/10.1007/978-1-4613-0303-9_27.
+
+>>> inst = Instance.from_resource("bur26a")
+>>> QAPObjective(inst).evaluate(np.array([
+...     25, 14, 10, 6, 3, 11, 12, 1, 5, 17, 0, 4, 8, 20,
+...     7, 13, 2, 19, 18, 24, 16, 9, 15, 23, 22, 21]))
+5426670
+
+>>> inst = Instance.from_resource("nug12")
+>>> QAPObjective(inst).evaluate(np.array([
+...     11, 6, 8, 2, 3, 7, 10, 0, 4, 5, 9, 1]))
+578
+
+>>> inst = Instance.from_resource("tai12a")
+>>> QAPObjective(inst).evaluate(np.array([
+...     7, 0, 5, 1, 10, 9, 2, 4, 8, 6, 11, 3]))
+224416
+"""
+
+
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.api.objective import Objective
+from moptipy.utils.logger import KeyValueLogSection
+from pycommons.types import type_error
+
+from moptipyapps.qap.instance import Instance
+from moptipyapps.shared import SCOPE_INSTANCE
+
+
+@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False)
+def _evaluate(x: np.ndarray, distances: np.ndarray, flows: np.ndarray) -> int:
+    """
+    Evaluate a solution to the QAP.
+
+    :param x: the permutation representing the solution
+    :param distances: the distance matrix
+    :param flows: the flow matrix
+    :return: the objective value
+    """
+    result: int = 0
+    for i, xi in enumerate(x):
+        for j, xj in enumerate(x):
+            result += flows[i, j] * distances[xi, xj]
+    return int(result)
+
+
+
+[docs] +class QAPObjective(Objective): + """An objective function for the quadratic assignment problem.""" + + def __init__(self, instance: Instance) -> None: + """ + Initialize the QAP objective function. + + :param instance: the one-dimensional ordering problem. + """ + super().__init__() + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + #: the instance data + self.instance: Final[Instance] = instance + +
+[docs] + def evaluate(self, x) -> float: + """ + Compute the quadratic assignment problem objective value. + + :param x: the permutation of elements + :return: the sum of flows times distances + """ + return _evaluate(x, self.instance.distances, self.instance.flows)
+ + + def __str__(self): + """ + Get the name of this objective. + + :return: `"qap"` + """ + return "qap" + +
+[docs] + def lower_bound(self) -> float: + """ + Get the lower bound of this objective function. + + :return: the lower bound + """ + return self.instance.lower_bound
+ + +
+[docs] + def upper_bound(self) -> float: + """ + Get the upper bound of this objective function. + + :return: the upper bound + """ + return self.instance.upper_bound
+ + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log all parameters of this component as key-value pairs. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + with logger.scope(SCOPE_INSTANCE) as scope: + self.instance.log_parameters_to(scope)
+
+ +
diff --git a/_modules/moptipyapps/qap/qaplib.html b/_modules/moptipyapps/qap/qaplib.html new file mode 100644 index 00000000..a0e6bbfe --- /dev/null +++ b/_modules/moptipyapps/qap/qaplib.html @@ -0,0 +1,32 @@ +moptipyapps.qap.qaplib — moptipyapps 0.8.62 documentation

Source code for moptipyapps.qap.qaplib

+"""
+QAPLIB -- A Quadratic Assignment Problem Library.
+
+1. QAPLIB - A Quadratic Assignment Problem Library. The Websites
+   <https://qaplib.mgi.polymtl.ca/> (updated 2018) and
+   <https://coral.ise.lehigh.edu/data-sets/qaplib/> (updated 2011), including
+   the benchmark instances, on visited 2023-10-21.
+2. Rainer E. Burkard, Stefan E. Karisch, and Franz Rendl. QAPLIB - A Quadratic
+   Assignment Problem Library. Journal of Global Optimization. 10:391-403,
+   1997. https://doi.org/10.1023/A:1008293323270.
+"""
+
+from importlib import resources  # nosem
+from typing import TextIO, cast
+
+from pycommons.io.path import UTF8
+
+
+
+[docs] +def open_resource_stream(file_name: str) -> TextIO: + """ + Open a QAPLib resource stream. + + :param file_name: the file name of the resource + :return: the stream + """ + return cast(TextIO, resources.files(__package__).joinpath( + file_name).open("r", encoding=UTF8))
+ +
diff --git a/_modules/moptipyapps/shared.html b/_modules/moptipyapps/shared.html new file mode 100644 index 00000000..76cd521a --- /dev/null +++ b/_modules/moptipyapps/shared.html @@ -0,0 +1,73 @@ +moptipyapps.shared — moptipyapps 0.8.62 documentation

Source code for moptipyapps.shared

+"""Some shared variables and constants."""
+
+import argparse
+from typing import Any, Final, Iterable
+
+import moptipy.examples.jssp.instance as ins
+from pycommons.io.arguments import make_argparser, make_epilog
+
+from moptipyapps.version import __version__ as moptipyapps_version
+
+#: the instance scope
+SCOPE_INSTANCE: Final[str] = ins.SCOPE_INSTANCE
+
+
+
+[docs] +def moptipyapps_argparser(file: str, description: str, + epilog: str) -> argparse.ArgumentParser: + """ + Create an argument parser with default settings. + + :param file: the `__file__` special variable of the calling script + :param description: the description string + :param epilog: the epilogue string + :returns: the argument parser + + >>> ap = moptipyapps_argparser( + ... __file__, "This is a test program.", "This is a test.") + >>> isinstance(ap, argparse.ArgumentParser) + True + >>> "Copyright" in ap.epilog + True + """ + return make_argparser( + file, description, + make_epilog(epilog, 2023, 2024, "Thomas Weise", + url="https://thomasweise.github.io/moptipyapps", + email="tweise@hfuu.edu.cn, tweise@ustc.edu.cn"), + moptipyapps_version)
+ + + + + +
diff --git a/_modules/moptipyapps/tests/.nojekyll b/_modules/moptipyapps/tests/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/moptipyapps/tests/on_binpacking2d.html b/_modules/moptipyapps/tests/on_binpacking2d.html new file mode 100644 index 00000000..8c4f17f9 --- /dev/null +++ b/_modules/moptipyapps/tests/on_binpacking2d.html @@ -0,0 +1,336 @@ +moptipyapps.tests.on_binpacking2d — moptipyapps 0.8.62 documentation

Source code for moptipyapps.tests.on_binpacking2d

+"""Perform tests on the Two-Dimensional Bin Packing Problem."""
+
+from time import monotonic_ns
+from typing import Callable, Final, Iterable, cast
+
+import numpy as np
+import numpy.random as rnd
+from moptipy.api.algorithm import Algorithm
+from moptipy.api.encoding import Encoding
+from moptipy.api.objective import Objective
+from moptipy.operators.signed_permutations.op0_shuffle_and_flip import (
+    Op0ShuffleAndFlip,
+)
+from moptipy.spaces.signed_permutations import SignedPermutations
+from moptipy.tests.algorithm import validate_algorithm
+from moptipy.tests.encoding import validate_encoding
+from moptipy.tests.objective import validate_objective
+from moptipy.tests.space import validate_space
+from numpy.random import Generator, default_rng
+from pycommons.types import type_error
+
+from moptipyapps.binpacking2d.encodings.ibl_encoding_1 import (
+    ImprovedBottomLeftEncoding1,
+)
+from moptipyapps.binpacking2d.encodings.ibl_encoding_2 import (
+    ImprovedBottomLeftEncoding2,
+)
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.objectives.bin_count_and_last_empty import (
+    BinCountAndLastEmpty,
+)
+from moptipyapps.binpacking2d.packing import Packing
+from moptipyapps.binpacking2d.packing_space import PackingSpace
+
+#: the internal random number generator
+__RANDOM: Final[Generator] = default_rng()
+
+
+
+[docs] +def binpacking_instances_for_tests( + random: Generator = __RANDOM) -> Iterable[str]: + """ + Get a sequence of 2D Bin Packing instances to test on. + + :param random: the random number generator to use + :returns: an iterable of 2D Bin Packing instance names + """ + ri = random.integers + insts: set[str] = { + "a04", "a08", "beng10", f"a0{ri(1, 10)}", f"a1{ri(1, 10)}", + f"a2{ri(1, 10)}", f"a3{ri(1, 10)}", f"a4{ri(1, 4)}", + f"beng0{ri(1, 10)}", f"cl01_080_0{ri(1, 10)}", + f"cl06_020_0{ri(1, 10)}", f"cl09_040_0{ri(1, 10)}", + f"cl10_100_0{ri(1, 10)}"} + insn: list[str] = list(Instance.list_resources()) + while len(insts) < 16: + insts.add(insn.pop(ri(len(insn)))) + use_insts: list[str] = list(insts) + random.shuffle(cast(list, use_insts)) + return use_insts
+ + + +
+[docs] +def make_packing_valid(inst: Instance, + random: Generator = __RANDOM) \ + -> Callable[[Generator, Packing], Packing]: + """ + Make a function that creates valid packings. + + :param inst: the two-dimensional bin packing instance + :param random: the random number generator to use + :returns: a function that can make packings valid + """ + search_space = SignedPermutations(inst.get_standard_item_sequence()) + encoding = (ImprovedBottomLeftEncoding1 if random.integers(2) == 0 + else ImprovedBottomLeftEncoding2)(inst) + op0 = Op0ShuffleAndFlip(search_space) + + def __make_valid(ra: rnd.Generator, + y: Packing, ss=search_space, + en=encoding, o0=op0) -> Packing: + x = ss.create() + o0.op0(ra, x) + en.decode(x, y) + return y + + return __make_valid
+ + + +
+[docs] +def make_packing_invalid(random: Generator = __RANDOM) \ + -> Callable[[Packing], Packing]: + """ + Make a function that creates invalid packings. + + :param random: the random number generator to use + :returns: a function that can make packings invalid + """ + + def __make_invalid(x: Packing, ri=random.integers) -> Packing: + not_finished: bool = True + end_time: Final[int] = monotonic_ns() + 20_000_000_000 + while not_finished: + while ri(2) == 0: + if monotonic_ns() >= end_time: + x[0, 0] = -1 + return x + x[ri(len(x)), ri(6)] = -1 + not_finished = False + while ri(2) == 0: + second = first = ri(len(x)) + while second == first: + if monotonic_ns() >= end_time: + x[0, 0] = -1 + return x + second = ri(len(x)) + x[first, 1] = x[second, 1] + x[first, 2] = x[second, 2] - 1 + x[first, 3] = x[second, 3] - 1 + x[first, 4] = x[second, 4] + 1 + x[first, 5] = x[second, 5] + 1 + return x + + return __make_invalid
+ + + +
+[docs] +def validate_algorithm_on_1_2dbinpacking( + algorithm: Algorithm | Callable[ + [Instance, SignedPermutations, Objective], Algorithm], + instance: str | None = None, max_fes: int = 100, + random: Generator = __RANDOM) -> None: + """ + Check the validity of a black-box algorithm on the 2d bin packing. + + :param algorithm: the algorithm or algorithm factory + :param instance: the instance name, or `None` to randomly pick one + :param max_fes: the maximum number of FEs + :param random: the default random generator to use + """ + if not (isinstance(algorithm, Algorithm) or callable(algorithm)): + raise type_error(algorithm, "algorithm", Algorithm, True) + if instance is None: + instance = str(random.choice(Instance.list_resources())) + if not isinstance(instance, str): + raise type_error(instance, "bin packing instance name", (str, None)) + inst = Instance.from_resource(instance) + if not isinstance(inst, Instance): + raise type_error(inst, f"loaded bin packing instance {instance!r}", + Instance) + + search_space = SignedPermutations(inst.get_standard_item_sequence()) + solution_space = PackingSpace(inst) + encoding = (ImprovedBottomLeftEncoding1 if random.integers(2) == 0 + else ImprovedBottomLeftEncoding2)(inst) + objective = BinCountAndLastEmpty(inst) + if callable(algorithm): + algorithm = algorithm(inst, search_space, objective) + if not isinstance(algorithm, Algorithm): + raise type_error(algorithm, "algorithm", Algorithm, call=True) + + validate_algorithm(algorithm=algorithm, + solution_space=solution_space, + objective=objective, + search_space=search_space, + encoding=encoding, + max_fes=max_fes)
+ + + +
+[docs] +def validate_algorithm_on_2dbinpacking( + algorithm: Callable[[Instance, SignedPermutations, + Objective], Algorithm], + max_fes: int = 100, random: Generator = __RANDOM) -> None: + """ + Validate an algorithm on a set of bin packing instances. + + :param algorithm: the algorithm factory + :param max_fes: the maximum FEs + :param random: the random number generator + """ + end_time: Final[int] = monotonic_ns() + 20_000_000_000 + for i in binpacking_instances_for_tests(random): + if monotonic_ns() >= end_time: + break + validate_algorithm_on_1_2dbinpacking(algorithm, i, max_fes, random)
+ + + +
+[docs] +def validate_objective_on_1_2dbinpacking( + objective: Objective | Callable[[Instance], Objective], + instance: str | None = None, + random: Generator = __RANDOM) -> None: + """ + Validate an objective function on 1 2D bin packing instance. + + :param objective: the objective function or a factory creating it + :param instance: the instance name + :param random: the random number generator + """ + if instance is None: + instance = str(random.choice(Instance.list_resources())) + if not isinstance(instance, str): + raise type_error(instance, "bin packing instance name", (str, None)) + inst = Instance.from_resource(instance) + if not isinstance(inst, Instance): + raise type_error(inst, f"loaded bin packing instance {instance!r}", + Instance) + + if callable(objective): + objective = objective(inst) + + validate_objective( + objective=objective, + solution_space=PackingSpace(inst), + make_solution_space_element_valid=make_packing_valid(inst), + is_deterministic=True)
+ + + +
+[docs] +def validate_objective_on_2dbinpacking( + objective: Objective | Callable[[Instance], Objective], + random: Generator = __RANDOM) -> None: + """ + Validate an objective function on bin packing instances. + + :param objective: the objective function or a factory creating it + :param random: the random number generator + """ + end_time: Final[int] = monotonic_ns() + 20_000_000_000 + for i in binpacking_instances_for_tests(random): + if monotonic_ns() >= end_time: + break + validate_objective_on_1_2dbinpacking(objective, i, random)
+ + + +
+[docs] +def validate_signed_permutation_encoding_on_1_2dbinpacking( + encoding: Encoding | Callable[[Instance], Encoding], + instance: str | None = None, + random: Generator = __RANDOM) -> None: + """ + Validate a signed permutation encoding on one 2D bin packing instance. + + :param encoding: the encoding or a factory creating it + :param instance: the instance name + :param random: the random number generator + """ + if instance is None: + instance = str(random.choice(Instance.list_resources())) + if not isinstance(instance, str): + raise type_error(instance, "bin packing instance name", (str, None)) + inst = Instance.from_resource(instance) + if not isinstance(inst, Instance): + raise type_error(inst, f"loaded bin packing instance {instance!r}", + Instance) + + if callable(encoding): + encoding = encoding(inst) + inst = Instance.from_resource(instance) + + x_space = SignedPermutations(inst.get_standard_item_sequence()) + validate_space(x_space) + + y_space = PackingSpace(inst) + validate_space(y_space, make_element_valid=None) + + validate_encoding(encoding, x_space, y_space) + + x = x_space.create() + x_space.validate(x) + + y = y_space.create() + encoding.decode(x, y) + y_space.validate(y) + + random.shuffle(x) + ri = random.integers + for i, xx in enumerate(x): + if ri(2) == 0: + x[i] = -xx + encoding.decode(x, y) + y_space.validate(y) + + x_str = x_space.to_str(x) + x_2 = x_space.from_str(x_str) + if not x_space.is_equal(x, x_2): + raise ValueError("error in space to/from_str") + if not np.array_equal(x, x_2): + raise ValueError("error in space to/from_str and is_equal") + + y_2 = y_space.create() + encoding.decode(x_2, y_2) + if not y_space.is_equal(y, y_2): + raise ValueError("encoding is not deterministic") + if not np.array_equal(y, y_2): + raise ValueError( + "encoding is not deterministic and error in space.is_equal")
+ + + +
+[docs] +def validate_signed_permutation_encoding_on_2dbinpacking( + encoding: Encoding | Callable[[Instance], Encoding], + random: Generator = __RANDOM) -> None: + """ + Validate a signed permutation encoding function on bin packing instances. + + :param encoding: the encoding or a factory creating it + :param random: the random number generator + """ + end_time: Final[int] = monotonic_ns() + 20_000_000_000 + for i in binpacking_instances_for_tests(random): + if monotonic_ns() >= end_time: + break + validate_signed_permutation_encoding_on_1_2dbinpacking( + encoding, i, random)
+ +
diff --git a/_modules/moptipyapps/tests/on_tsp.html b/_modules/moptipyapps/tests/on_tsp.html new file mode 100644 index 00000000..82d82623 --- /dev/null +++ b/_modules/moptipyapps/tests/on_tsp.html @@ -0,0 +1,229 @@ +moptipyapps.tests.on_tsp — moptipyapps 0.8.62 documentation

Source code for moptipyapps.tests.on_tsp

+"""Perform tests on the Traveling Salesperson Problem."""
+
+from time import monotonic_ns
+from typing import Callable, Final, Iterable
+
+import numpy as np
+from moptipy.api.algorithm import Algorithm
+from moptipy.api.objective import Objective
+from moptipy.spaces.permutations import Permutations
+from moptipy.tests.algorithm import validate_algorithm
+from moptipy.tests.objective import validate_objective
+from numpy.random import Generator, default_rng
+from pycommons.types import type_error
+
+from moptipyapps.tsp.instance import Instance
+from moptipyapps.tsp.tour_length import TourLength
+
+#: the internal random number generator
+__RANDOM: Final[Generator] = default_rng()
+
+
+
+[docs] +def tsp_instances_for_tests( + random: Generator = __RANDOM, + symmetric: bool = True, + asymmetric: bool = True) -> Iterable[str]: + """ + Get a sequence of TSP instances to test on. + + :param random: the random number generator to use + :param symmetric: include symmetric instances + :param asymmetric: include asymmetric instances + :returns: an iterable of TSP instance names + """ + if not isinstance(symmetric, bool): + raise type_error(symmetric, "symmetric", bool) + if not isinstance(asymmetric, bool): + raise type_error(asymmetric, "asymmetric", bool) + + instances: tuple[str, ...] + if symmetric and asymmetric: + instances = Instance.list_resources(True, True) + elif symmetric: + instances = Instance.list_resources(True, False) + elif asymmetric: + instances = Instance.list_resources(False, True) + else: + raise ValueError( + "at least of one symmetric or asymmetric must be TRUE") + use_insts = list(instances) + while len(use_insts) > 20: + del use_insts[random.integers(len(use_insts))] + random.shuffle(use_insts) + return use_insts
+ + + +
+[docs] +def make_tour_valid(random: Generator, y: np.ndarray) -> np.ndarray: + """ + Create valid TSP tours. + + :param random: the random number generator to use + :param y: the input tour + :returns: the valid version of `y` + """ + y[0:len(y)] = range(len(y)) + random.shuffle(y) + return y
+ + + +
+[docs] +def make_tour_invalid(random: Generator, y: np.ndarray) -> np.ndarray: + """ + Create invalid tours. + + :param random: the random number generator to use + :param y: the input tour + :returns: the invalid version of `y` + """ + ly: Final[int] = len(y) + y[0:ly] = range(ly) + random.shuffle(y) + yorig = np.copy(y) + + ri = random.integers + end_time: Final[int] = monotonic_ns() + 20_000_000_000 + while np.all(y == yorig): + if monotonic_ns() >= end_time: + y[0] = y[1] + return y + if ri(2) <= 0: + z1 = z2 = ri(ly) + while z1 == z2: + if monotonic_ns() >= end_time: + y[0] = y[1] + return y + z2 = ri(ly) + y[z1] = y[z2] + if ri(2) <= 0: + y[ri(ly)] = ri(ly, 10 * ly) + if ri(2) <= 0: + y[ri(ly)] = ri(-2 * ly, -1) + return y
+ + + +
+[docs] +def validate_algorithm_on_1_tsp( + algorithm: Algorithm | Callable[ + [Instance, Permutations], Algorithm], + instance: str | None = None, max_fes: int = 256, + random: Generator = __RANDOM) -> None: + """ + Check the validity of a black-box algorithm on one TSP instance. + + :param algorithm: the algorithm or algorithm factory + :param instance: the instance name, or `None` to randomly pick one + :param max_fes: the maximum number of FEs + :param random: the default random generator to use + """ + if not (isinstance(algorithm, Algorithm) or callable(algorithm)): + raise type_error(algorithm, "algorithm", Algorithm, True) + if instance is None: + instance = str(random.choice(Instance.list_resources())) + if not isinstance(instance, str): + raise type_error(instance, "TSP instance name", (str, None)) + inst = Instance.from_resource(instance) + if not isinstance(inst, Instance): + raise type_error(inst, f"loaded bin TSP instance {instance!r}", + Instance) + + search_space = Permutations.standard(inst.n_cities) + objective = TourLength(inst) + if callable(algorithm): + algorithm = algorithm(inst, search_space) + if not isinstance(algorithm, Algorithm): + raise type_error(algorithm, "algorithm", Algorithm, call=True) + + validate_algorithm(algorithm=algorithm, + solution_space=search_space, + objective=objective, + max_fes=max_fes)
+ + + +
+[docs] +def validate_algorithm_on_tsp( + algorithm: Callable[[Instance, Permutations], Algorithm], + symmetric: bool = True, asymmetric: bool = True, + max_fes: int = 256, random: Generator = __RANDOM) -> None: + """ + Validate an algorithm on a set of TSP instances. + + :param algorithm: the algorithm factory + :param symmetric: include symmetric instances + :param asymmetric: include asymmetric instances + :param max_fes: the maximum FEs + :param random: the random number generator + """ + end_time: Final[int] = monotonic_ns() + 20_000_000_000 + for i in tsp_instances_for_tests(random, symmetric, asymmetric): + if monotonic_ns() >= end_time: + return + validate_algorithm_on_1_tsp(algorithm, i, max_fes, random)
+ + + +
+[docs] +def validate_objective_on_1_tsp( + objective: Objective | Callable[[Instance], Objective], + instance: str | None = None, + random: Generator = __RANDOM) -> None: + """ + Validate an objective function on 1 TSP instance. + + :param objective: the objective function or a factory creating it + :param instance: the instance name + :param random: the random number generator + """ + if instance is None: + instance = str(random.choice(Instance.list_resources())) + if not isinstance(instance, str): + raise type_error(instance, "TSP instance name", (str, None)) + inst = Instance.from_resource(instance) + if not isinstance(inst, Instance): + raise type_error(inst, f"loaded TSP instance {instance!r}", + Instance) + + if callable(objective): + objective = objective(inst) + + validate_objective( + objective=objective, + solution_space=Permutations.standard(inst.n_cities), + make_solution_space_element_valid=make_tour_valid, + is_deterministic=True)
+ + + +
+[docs] +def validate_objective_on_tsp( + objective: Objective | Callable[[Instance], Objective], + symmetric: bool = True, asymmetric: bool = True, + random: Generator = __RANDOM) -> None: + """ + Validate an objective function on TSP instances. + + :param objective: the objective function or a factory creating it + :param symmetric: include symmetric instances + :param asymmetric: include asymmetric instances + :param random: the random number generator + """ + end_time: Final[int] = monotonic_ns() + 20_000_000_000 + for i in tsp_instances_for_tests(random, symmetric, asymmetric): + if monotonic_ns() >= end_time: + return + validate_objective_on_1_tsp(objective, i, random)
+ +
diff --git a/_modules/moptipyapps/tsp/.nojekyll b/_modules/moptipyapps/tsp/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/moptipyapps/tsp/ea1p1_revn.html b/_modules/moptipyapps/tsp/ea1p1_revn.html new file mode 100644 index 00000000..c007dd3d --- /dev/null +++ b/_modules/moptipyapps/tsp/ea1p1_revn.html @@ -0,0 +1,168 @@ +moptipyapps.tsp.ea1p1_revn — moptipyapps 0.8.62 documentation

Source code for moptipyapps.tsp.ea1p1_revn

+"""
+A (1+1) EA for the TSP using the reversal move.
+
+A (1+1) EA is basically a local search :mod:`~moptipy.algorithms.so.rls`
+algorithm that starts with a random solution as the current solution `x`. In
+each iteration, it samples one new solution from the neighborhood of `x` and
+accepts it (as the new `x`), if it is better or equally good.
+The algorithm here applies the reversal move, i.e., a two-opt move, as the
+unary search operator used for sampling the neighborhood of `x`. The
+interesting thing about this operator, when applied to the TSP, is that we
+can compute the tour length of the new solution in `O(1)` from the tour length
+of the current solution. This means we can very quickly decide whether the
+move would be acceptable - and only apply it (in `O(n)`) if its.
+
+The algorithm implemented here is the same as the basic (1+1) EA with `rev`
+operator in the paper [1].
+
+The original version of this code has been contributed by Mr. Tianyu LIANG
+(梁天宇), <liangty@stu.hfuu.edu.cn> a Master's student at the Institute of
+Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School
+of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei
+University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the
+supervision of Prof. Dr. Thomas Weise (汤卫思教授).
+
+1. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, and Thomas Weise.
+   Solving the Traveling Salesperson Problem using Frequency Fitness
+   Assignment. In Hisao Ishibuchi, Chee-Keong Kwoh, Ah-Hwee Tan, Dipti
+   Srinivasan, Chunyan Miao, Anupam Trivedi, and Keeley A. Crockett, editors,
+   *Proceedings of the IEEE Symposium on Foundations of Computational
+   Intelligence (IEEE FOCI'22)*, part of the *IEEE Symposium Series on
+   Computational Intelligence (SSCI 2022)*. December 4-7, 2022, Singapore,
+   pages 360-367. IEEE. https://doi.org/10.1109/SSCI51031.2022.10022296.
+2. Pedro Larrañaga, Cindy M. H. Kuijpers, Roberto H. Murga, Iñaki Inza, and
+   S. Dizdarevic. Genetic Algorithms for the Travelling Salesman Problem: A
+   Review of Representations and Operators. *Artificial Intelligence Review,*
+   13(2):129-170, April 1999. Kluwer Academic Publishers, The Netherlands.
+   https://doi.org/10.1023/A:1006529012972.
+"""
+
+from typing import Callable, Final, cast
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.api.algorithm import Algorithm
+from moptipy.api.process import Process
+from moptipy.utils.logger import KeyValueLogSection
+from numpy.random import Generator
+from pycommons.types import type_error
+
+from moptipyapps.shared import SCOPE_INSTANCE
+from moptipyapps.tsp.instance import Instance
+
+
+
+[docs] +@numba.njit(nogil=True, cache=True, inline="always", fastmath=False, + boundscheck=False) +def rev_if_not_worse(i: int, j: int, n_cities: int, dist: np.ndarray, + x: np.ndarray, y: int) -> int: + """ + Check a reversal move, apply it if it is better, return new tour length. + + :param i: the first, smaller index + :param j: the second, larger index + :param n_cities: the number of cities + :param dist: the problem instance + :param x: the candidate solution + :param y: the tour length + """ + xi: Final[int] = x[i] # the value of x at index i + xim1: Final[int] = x[i - 1] # the value of x at index i-1, wraps at 0 + xj: Final[int] = x[j] # the value of x at index j + xjp1: Final[int] = x[(j + 1) % n_cities] # x[j + 1] with index wrap + + # compute the difference in tour length if we would apply the move + dy: Final[int] = (dist[xim1, xj] + dist[xi, xjp1] + - dist[xim1, xi] - dist[xj, xjp1]) + if dy <= 0: # this is not a worsening improving move? ... so apply it + # reverse the sequence from i to j in the solution + if i == 0: # deal with the special case that i==0 + x[0:j + 1:1] = x[j::-1] + else: # the normal case that i > 0 + x[i:j + 1:1] = x[j:i - 1:-1] + return int(y + dy) # return new tour length + return y # return old tour length
+ + + +
+[docs] +class TSPEA1p1revn(Algorithm): + """A (1+1) EA using the reversal operator for the TSP.""" + + def __init__(self, instance: Instance) -> None: + """ + Initialize the RLS algorithm for the TSP with reversal move. + + :param instance: the problem instance + """ + super().__init__() + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + self.instance: Final[Instance] = instance + +
+[docs] + def solve(self, process: Process) -> None: + """ + Apply an RLS = (1+1) EA optimization process with reversing operator. + + :param process: the process instance which provides random numbers, + functions for creating, copying, and evaluating solutions, as well + as the termination criterion + """ + random: Final[Generator] = process.get_random() + # set up the fast calls + register: Final[Callable[[np.ndarray, int], int]] =\ + cast(Callable[[np.ndarray, int], int], process.register) + should_terminate: Final[Callable[[], bool]] = process.should_terminate + ri: Final[Callable[[int], int]] = random.integers + + instance: Final[Instance] = self.instance # get the instance + n: Final[int] = instance.n_cities # get the number of cities + x: Final[np.ndarray] = process.create() # create the solution + x[:] = range(n) # fill array with 0..n + random.shuffle(x) # randomly generate an initial solution + + y: int = cast(int, process.evaluate(x)) # get length of first tour + nm1: Final[int] = n - 1 # need n-1 in the loop for the random numbers + nm2: Final[int] = n - 2 # we need this to check the move indices + while not should_terminate(): + i = ri(nm1) # get the first index + j = ri(nm1) # get the second index + if i > j: # ensure that i <= j + i, j = j, i # swap indices if i > j + if (i == j) or ((i == 0) and (j == nm2)): + continue # either a nop or a complete reversal + y = rev_if_not_worse(i, j, n, instance, x, y) # apply the move + register(x, y) # register the objective value
+ + + def __str__(self): + """ + Get the name of this algorithm. + + This name is then used in the directory path and file name of the + log files. + + :returns: "tsp_ea1p1_revn" + :retval "tsp_ea1p1_revn": always + """ + return "tsp_ea1p1_revn" + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log the parameters of the algorithm to the given logger. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + with logger.scope(SCOPE_INSTANCE) as kv: + self.instance.log_parameters_to(kv)
+
+ +
diff --git a/_modules/moptipyapps/tsp/fea1p1_revn.html b/_modules/moptipyapps/tsp/fea1p1_revn.html new file mode 100644 index 00000000..4f480943 --- /dev/null +++ b/_modules/moptipyapps/tsp/fea1p1_revn.html @@ -0,0 +1,180 @@ +moptipyapps.tsp.fea1p1_revn — moptipyapps 0.8.62 documentation

Source code for moptipyapps.tsp.fea1p1_revn

+"""
+A (1+1) FEA for the TSP using the reversal move.
+
+A (1+1) FEA is the same as the (1+1) EA with Frequency Fitness Assignment.
+The (1+1) EA using the same search operator as this algorithm here is
+implemented in module :mod:`~moptipyapps.tsp.ea1p1_revn`.
+The algorithm implemented here is the same as the basic (1+1) FEA with `rev`
+operator in the paper [1].
+
+The original version of this code has been contributed by Mr. Tianyu LIANG
+(梁天宇), <liangty@stu.hfuu.edu.cn> a Master's student at the Institute of
+Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School
+of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei
+University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the
+supervision of Prof. Dr. Thomas Weise (汤卫思教授).
+
+1. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, and Thomas Weise.
+   Solving the Traveling Salesperson Problem using Frequency Fitness
+   Assignment. In Hisao Ishibuchi, Chee-Keong Kwoh, Ah-Hwee Tan, Dipti
+   Srinivasan, Chunyan Miao, Anupam Trivedi, and Keeley A. Crockett, editors,
+   *Proceedings of the IEEE Symposium on Foundations of Computational
+   Intelligence (IEEE FOCI'22)*, part of the *IEEE Symposium Series on
+   Computational Intelligence (SSCI 2022)*. December 4-7, 2022, Singapore,
+   pages 360-367. IEEE. https://doi.org/10.1109/SSCI51031.2022.10022296.
+2. Pedro Larrañaga, Cindy M. H. Kuijpers, Roberto H. Murga, Iñaki Inza, and
+   S. Dizdarevic. Genetic Algorithms for the Travelling Salesman Problem: A
+   Review of Representations and Operators. *Artificial Intelligence Review,*
+   13(2):129-170, April 1999. Kluwer Academic Publishers, The Netherlands.
+   https://doi.org/10.1023/A:1006529012972.
+"""
+
+from typing import Callable, Final, cast
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.algorithms.so.ffa.ffa_h import log_h
+from moptipy.api.algorithm import Algorithm
+from moptipy.api.process import Process
+from moptipy.utils.logger import KeyValueLogSection
+from moptipy.utils.nputils import DEFAULT_INT
+from numpy.random import Generator
+from pycommons.types import type_error
+
+from moptipyapps.shared import SCOPE_INSTANCE
+from moptipyapps.tsp.instance import Instance
+
+
+
+[docs] +@numba.njit(nogil=True, cache=True, inline="always", fastmath=False, + boundscheck=False) +def rev_if_h_not_worse(i: int, j: int, n_cities: int, dist: np.ndarray, + h: np.ndarray, x: np.ndarray, y: int) -> int: + """ + Apply a reversal move if its tour length frequency is not worse. + + :param i: the first, smaller index + :param j: the second, larger index + :param n_cities: the number of cities + :param dist: the problem instance + :param h: the frequency table + :param x: the candidate solution + :param y: the tour length + """ + xi: Final[int] = x[i] # the value of x at index i + xim1: Final[int] = x[i - 1] # the value of x at index i-1, wraps at 0 + xj: Final[int] = x[j] # the value of x at index j + xjp1: Final[int] = x[(j + 1) % n_cities] # x[j + 1], but index wrapped + + # compute the difference in tour length if we would apply the move + dy: Final[int] = (dist[xim1, xj] + dist[xi, xjp1] + - dist[xim1, xi] - dist[xj, xjp1]) + y2: Final[int] = int(y + dy) # and compute the new tour length + h[y] += 1 # update frequency of the tour length of the current solution + h[y2] += 1 # update frequency of the tour length of the new solution + if h[y2] <= h[y]: # this move does not make the frequency worse? + # so we reverse the sequence from i to j in the solution + if i == 0: # deal with the special case that i==0 + x[0:j + 1:1] = x[j::-1] + else: # the normal case that i > 0 + x[i:j + 1:1] = x[j:i - 1:-1] + return y2 # return new tour length + return y # return old tour length
+ + + +
+[docs] +class TSPFEA1p1revn(Algorithm): + """A (1+1) FEA using the reversal operator for the TSP.""" + + def __init__(self, instance: Instance, + do_log_h: bool = False) -> None: + """ + Initialize the RLS algorithm for the TSP with reversal move. + + :param instance: the problem instance + :param do_log_h: shall we log the `h`-table? + """ + super().__init__() + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + #: the instance + self.instance: Final[Instance] = instance + #: shall we log the `h`-table`? + self.do_log_h: Final[bool] = do_log_h + +
+[docs] + def solve(self, process: Process) -> None: + """ + Apply a (1+1) FEA optimization process with reversing operator. + + :param process: the process instance which provides random numbers, + functions for creating, copying, and evaluating solutions, as well + as the termination criterion + """ + random: Final[Generator] = process.get_random() + # set up the fast calls + register: Final[Callable[[np.ndarray, int], int]] =\ + cast(Callable[[np.ndarray, int], int], process.register) + should_terminate: Final[Callable[[], bool]] = process.should_terminate + ri: Final[Callable[[int], int]] = random.integers + + instance: Final[Instance] = self.instance # get the instance + h: Final[np.ndarray] = np.zeros( # allocate the frequency table + instance.tour_length_upper_bound + 1, DEFAULT_INT) + n: Final[int] = instance.n_cities # get the number of cities + x: Final[np.ndarray] = process.create() # create the solution + x[:] = range(n) # fill array with 0..n + random.shuffle(x) # randomly generate an initial solution + + y: int = cast(int, process.evaluate(x)) # get length of first tour + nm1: Final[int] = n - 1 # need n-1 in the loop for the random numbers + nm2: Final[int] = n - 2 # we need this to check the move indices + while not should_terminate(): + i = ri(nm1) # get the first index + j = ri(nm1) # get the second index + if i > j: # ensure that i <= j + i, j = j, i # swap indices if i > j + if (i == j) or ((i == 0) and (j == nm2)): + continue # either a nop or a complete reversal + y = rev_if_h_not_worse(i, j, n, instance, h, x, y) # move + register(x, y) # register the objective value + + if self.do_log_h: + # we log the frequency table at the very end of the run + if h[y] == 0: + h[y] = 1 + log_h(process, h, 0)
+ + + def __str__(self): + """ + Get the name of this algorithm. + + This name is then used in the directory path and file name of the + log files. + + :returns: "tsp_fea1p1_revn" + :retval "tsp_fea1p1_revn": always + """ + return "tsp_fea1p1_revn" + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log the parameters of the algorithm to the given logger. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + logger.key_value("doLogH", self.do_log_h) + with logger.scope(SCOPE_INSTANCE) as kv: + self.instance.log_parameters_to(kv)
+
+ +
diff --git a/_modules/moptipyapps/tsp/instance.html b/_modules/moptipyapps/tsp/instance.html new file mode 100644 index 00000000..5a2a5e8b --- /dev/null +++ b/_modules/moptipyapps/tsp/instance.html @@ -0,0 +1,1156 @@ +moptipyapps.tsp.instance — moptipyapps 0.8.62 documentation

Source code for moptipyapps.tsp.instance

+"""
+An instance of the Traveling Salesperson Problem (TSP) as distance matrix.
+
+An instance of the Traveling Salesperson Problem (TSP) is defined as a
+fully-connected graph with :attr:`Instance.n_cities` nodes. Each edge in the
+graph has a weight, which identifies the distance between the nodes. The goal
+is to find the *shortest* tour that visits every single node in the graph
+exactly once and then returns back to its starting node. Then nodes are
+usually called cities. In this file, we present methods for loading instances
+of the TSP as distance matrices `A`. In other words, the value at `A[i, j]`
+identifies the travel distance from `i` to `j`.
+
+We can load files in a subset of the TSPLib format [1, 2] and also include the
+instances of TSPLib with no more than 2000 cities. We load instances as full
+distance matrices, which makes writing algorithms to solve them easier but
+handling instances with more than 2000 cities problematic due to the high
+memory consumption. Of course, you could still load them, but it would be
+ill-advised to do so on a normal PC (at the time of this writing).
+
+The TSPLib contains many instances of the symmetric TSP, where the distance
+`A[i, j]` from city `i` to city `j` is the same as the distance `A[j, i]` from
+`j` to `i`. Here, we provide 111 of them as resource. The cities of some of
+these instances are points in the Euclidean plain and the distances are the
+(approximate) Euclidean distances. Others use geographical coordinates
+(longitude/latitude), and yet others are provides as distances matrices
+directly. We also provide 19 of the asymmetric TSPLib instances, where the
+distance `A[i, j]` from `i` to `j` may be different from the distance
+`A[j, i]` from `j` to `i`.
+
+You can obtain lists of either all, only the symmetric, or only the asymmetric
+instance resources via
+
+>>> print(Instance.list_resources()[0:10])  # get all instances (1..10)
+('a280', 'ali535', 'att48', 'att532', 'bayg29', 'bays29', 'berlin52', \
+'bier127', 'br17', 'brazil58')
+
+>>> print(Instance.list_resources(asymmetric=False)[0:10])  # only symmetric
+('a280', 'ali535', 'att48', 'att532', 'bayg29', 'bays29', 'berlin52', \
+'bier127', 'brazil58', 'brg180')
+
+>>> print(Instance.list_resources(symmetric=False)[0:10])  # only asymmetric
+('br17', 'ft53', 'ft70', 'ftv170', 'ftv33', 'ftv35', 'ftv38', 'ftv44', \
+'ftv47', 'ftv55')
+
+You can load any of these instances from the resources via
+:meth:`Instance.from_resource` as follows:
+
+>>> inst = Instance.from_resource("a280")
+>>> print(inst.n_cities)
+280
+
+If you want to read an instance from an external TSPLib file, you can use
+:meth:`Instance.from_file`. Be aware that not the whole TSPLib standard is
+supported right now, but only a reasonably large subset.
+
+Every TSP instance automatically provides a lower bound
+:attr:`Instance.tour_length_lower_bound` and an upper bound
+:attr:`Instance.tour_length_upper_bound` of the lengths of valid tours.
+For the TSPLib instances, the globally optimal solutions and their tour
+lengths are known, so we can use them as lower bounds directly. Otherwise,
+we currently use a very crude approximation: We assume that, for each city
+`i`, the next city `j` to be visited would be the nearest neighbor of `i`.
+Of course, in reality, such a tour may not be feasible, as it could contain
+disjoint sets of loops. But no tour can be shorter than this.
+As upper bound, we do the same but assume that `j` would be the cities
+farthest away from `i`.
+
+>>> print(inst.tour_length_lower_bound)
+2579
+>>> print(inst.tour_length_upper_bound)
+65406
+
+It should be noted that all TSPLib instances by definition have integer
+distances. This means that there are never floating point distances and, for
+example, Euclidean distances are rounded and are only approximate Euclidean.
+Then again, since floating point numbers could also only represent things such
+as `sqrt(2)` approximately, using integers instead of floating point numbers
+does not really change anything - distances would be approximately Euclidean
+or approximately geographical either way.
+
+TSPLib also provides some known optimal solutions in path representation,
+i.e., as permutations of the numbers `0` to `n_cities-1`. The optimal
+solutions corresponding to the instances provides as resources can be obtained
+via :mod:`~moptipyapps.tsp.known_optima`.
+
+>>> inst = Instance.from_resource("si175")
+>>> print(inst.n_cities)
+175
+>>> inst[0, 1]
+113
+>>> inst[173, 174]
+337
+>>> inst[1, 174]
+384
+>>> inst[2, 174]
+384
+>>> inst[2, 3]
+248
+>>> inst[3, 5]
+335
+>>> inst[4, 6]
+134
+
+The original data of TSPLib can be found at
+<http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/>. Before doing
+anything with these data directly, you should make sure to read the FAQ
+<http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/TSPFAQ.html> and the
+documentation
+<http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp95.pdf>.
+
+On 2024-10-18, we added a small eleven city instance based on the car travel
+distances in km between the Chinese cities
+Shanghai (上海), Beijing (北京), Nanjing (南京), Hefei (合肥),
+Harbin (哈尔滨), Kunming (昆明), Wuhan (武汉),
+Xi'an (西安), Chongqing (重庆), Changsha (长沙),
+and Hong Kong (香港), determined using BaiDu Maps on 2024-10-18.
+The optimal solution for this instance has length 9547 (km) and visits the
+cities in the following order: Shanghai (上海),
+Nanjing (南京), Hefei (合肥), Wuhan (武汉), Changsha (长沙),
+Hong Kong (香港), Kunming (昆明), Chongqing (重庆),
+Xi'an (西安), Beijing (北京), Harbin (哈尔滨).
+This instance can be used to validate and santity-test algorithms, as its
+solution can easily be determined using exhaustive enumeration.
+
+Interestingly, it holds that the calculated travel distance from
+Harbin (哈尔滨) to Kunming (昆明) is longer than the travel distance
+from Harbin (哈尔滨) to Xi'an (西安) and then from
+Xi'an (西安) to Kunming (昆明).
+
+- Harbin (哈尔滨) - Kunming (昆明) = 3781 km
+- Harbin (哈尔滨) - Xi'an (西安) = 2291 km
+- Xi'an (西安) - Kunming (昆明) = 1483 km
+- 2291 km + 1483 km = 3774 km, which is less than 3781 km
+
+This may be because BaiduMaps ranked the paths by travel time and not travel
+distance, or by any other strange effect I cannot account for. Maybe it is
+related to which lane of a road one would naturally drive on or lengths of
+intersections depending on the direction one is coming from.
+Either way, this shows that this simple instance `cn11` may already have
+interesting properties. It violates the triangle equation and it is
+non-Euclidean. It is also not based on Geo-coordinates, but on actual street
+distances and times.
+
+>>> inst = Instance.from_resource("cn11")
+>>> inst[0, 0]
+0
+>>> inst[1, 2]
+1007
+>>> inst[1, 3]
+1017
+>>> inst[9, 10]
+830
+>>> inst[5, 6]
+1560
+
+Important initial work on this code has been contributed by Mr. Tianyu LIANG
+(梁天宇), <liangty@stu.hfuu.edu.cn> a Master's student at the Institute of
+Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School
+of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei
+University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the
+supervision of Prof. Dr. Thomas Weise (汤卫思教授).
+
+1. Gerhard Reinelt. TSPLIB - A Traveling Salesman Problem Library.
+   *ORSA Journal on Computing* 3(4):376-384. November 1991.
+   https://doi.org/10.1287/ijoc.3.4.376.
+   http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/
+2. Gerhard Reinelt. *TSPLIB95.* Heidelberg, Germany: Universität
+   Heidelberg, Institut für Angewandte Mathematik. 1995.
+   http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp95.pdf
+3. David Lee Applegate, Robert E. Bixby, Vašek Chvátal, and William John Cook.
+   *The Traveling Salesman Problem: A Computational Study.* Second Edition,
+   2007. Princeton, NJ, USA: Princeton University Press. Volume 17 of
+   Princeton Series in Applied Mathematics. ISBN: 0-691-12993-2.
+4. Gregory Z. Gutin and Abraham P. Punnen. *The Traveling Salesman Problem and
+   its Variations.* 2002. Kluwer Academic Publishers. Volume 12 of
+   Combinatorial Optimization. https://doi.org/10.1007/b101971.
+5. Pedro Larrañaga, Cindy M. H. Kuijpers, Roberto H. Murga, Iñaki Inza, and
+   Sejla Dizdarevic. Genetic Algorithms for the Travelling Salesman Problem: A
+   Review of Representations and Operators. *Artificial Intelligence Review*
+   13(2):129-170. April 1999. https://doi.org/10.1023/A:1006529012972.
+6. Eugene Leighton Lawler, Jan Karel Lenstra, Alexander Hendrik George Rinnooy
+   Kan, and David B. Shmoys. *The Traveling Salesman Problem: A Guided Tour of
+   Combinatorial Optimization.* September 1985. Chichester, West Sussex, UK:
+   Wiley Interscience. In Estimation, Simulation, and
+   Control - Wiley-Interscience Series in Discrete Mathematics and
+   Optimization. ISBN: 0-471-90413-9
+7. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, and Thomas Weise.
+   Solving the Traveling Salesperson Problem using Frequency Fitness
+   Assignment. In *Proceedings of the IEEE Symposium on Foundations of
+   Computational Intelligence (IEEE FOCI'22),* part of the *IEEE Symposium
+   Series on Computational Intelligence (SSCI'22),* December 4-7, 2022,
+   Singapore. Pages 360-367. IEEE.
+   https://doi.org/10.1109/SSCI51031.2022.10022296.
+8. Thomas Weise, Raymond Chiong, Ke Tang, Jörg Lässig, Shigeyoshi Tsutsui,
+   Wenxiang Chen, Zbigniew Michalewicz, and Xin Yao. Benchmarking Optimization
+   Algorithms: An Open Source Framework for the Traveling Salesman Problem.
+   *IEEE Computational Intelligence Magazine.* 9(3):40-52. August 2014.
+   https://doi.org/10.1109/MCI.2014.2326101.
+"""
+
+from math import acos, cos, isfinite, sqrt
+from typing import Any, Callable, Final, Iterable, TextIO, cast
+
+import moptipy.utils.nputils as npu
+import numpy as np
+from moptipy.api.component import Component
+from moptipy.utils.logger import KeyValueLogSection
+from moptipy.utils.nputils import int_range_to_dtype
+from moptipy.utils.strings import sanitize_name
+from pycommons.io.path import Path, file_path
+from pycommons.types import check_int_range, check_to_int_range, type_error
+
+from moptipyapps.tsp.tsplib import open_resource_stream
+
+#: the known optimal tour lengths and lower bounds of TSP Lib
+_LOWER_BOUNDS: dict[str, int] = {
+    "a280": 2579, "ali535": 202339, "att48": 10628, "att532": 27686,
+    "bayg29": 1610, "bays29": 2020, "berlin52": 7542, "bier127": 118282,
+    "br17": 39, "brazil58": 25395, "brd14051": 469385, "brg180": 1950,
+    "burma14": 3323, "ch130": 6110, "ch150": 6528, "cn11": 9547,
+    "d1291": 50801, "d15112": 1573084, "d1655": 62128, "d18512": 645238,
+    "d198": 15780, "d2103": 80450, "d493": 35002, "d657": 48912,
+    "dantzig42": 699, "dsj1000": 18660188, "eil101": 629, "eil51": 426,
+    "eil76": 538, "fl1400": 20127, "fl1577": 22249, "fl3795": 28772,
+    "fl417": 11861, "fnl4461": 182566, "fri26": 937, "ft53": 6905,
+    "ft70": 38673, "ftv100": 1788, "ftv110": 1958, "ftv120": 2166,
+    "ftv130": 2307, "ftv140": 2420, "ftv150": 2611, "ftv160": 2683,
+    "ftv170": 2755, "ftv33": 1286, "ftv35": 1473, "ftv38": 1530,
+    "ftv44": 1613, "ftv47": 1776, "ftv55": 1608, "ftv64": 1839,
+    "ftv70": 1950, "ftv90": 1579, "gil262": 2378, "gr120": 6942,
+    "gr137": 69853, "gr17": 2085, "gr202": 40160, "gr21": 2707,
+    "gr229": 134602, "gr24": 1272, "gr431": 171414, "gr48": 5046,
+    "gr666": 294358, "gr96": 55209, "hk48": 11461, "kro124p": 36230,
+    "kroA100": 21282, "kroA150": 26524, "kroA200": 29368, "kroB100": 22141,
+    "kroB150": 26130, "kroB200": 29437, "kroC100": 20749, "kroD100": 21294,
+    "kroE100": 22068, "lin105": 14379, "lin318": 42029, "nrw1379": 56638,
+    "p43": 5620, "p654": 34643, "pa561": 2763, "pcb1173": 56892,
+    "pcb3038": 137694, "pcb442": 50778, "pla33810": 66048945,
+    "pla7397": 23260728, "pla85900": 142382641, "pr1002": 259045,
+    "pr107": 44303, "pr124": 59030, "pr136": 96772, "pr144": 58537,
+    "pr152": 73682, "pr226": 80369, "pr2392": 378032, "pr264": 49135,
+    "pr299": 48191, "pr439": 107217, "pr76": 108159, "rat195": 2323,
+    "rat575": 6773, "rat783": 8806, "rat99": 1211, "rbg323": 1326,
+    "rbg358": 1163, "rbg403": 2465, "rbg443": 2720, "rd100": 7910,
+    "rd400": 15281, "rl11849": 923288, "rl1304": 252948, "rl1323": 270199,
+    "rl1889": 316536, "rl5915": 565530, "rl5934": 556045, "ry48p": 14422,
+    "si1032": 92650, "si175": 21407, "si535": 48450, "st70": 675,
+    "swiss42": 1273, "ts225": 126643, "tsp225": 3916, "u1060": 224094,
+    "u1432": 152970, "u159": 42080, "u1817": 57201, "u2152": 64253,
+    "u2319": 234256, "u574": 36905, "u724": 41910, "ulysses16": 6859,
+    "ulysses22": 7013, "usa13509": 19982859, "vm1084": 239297,
+    "vm1748": 336556}
+
+#: the TSPLib instances
+_SYMMETRIC_INSTANCES: Final[tuple[str, ...]] = (
+    "a280", "ali535", "att48", "att532", "bayg29", "bays29", "berlin52",
+    "bier127", "brazil58", "brg180", "burma14", "ch130", "ch150", "cn11",
+    "d1291", "d1655", "d198", "d493", "d657", "dantzig42", "eil101", "eil51",
+    "eil76", "fl1400", "fl1577", "fl417", "fri26", "gil262", "gr120", "gr137",
+    "gr17", "gr202", "gr21", "gr229", "gr24", "gr431", "gr48", "gr666",
+    "gr96", "hk48", "kroA100", "kroA150", "kroA200", "kroB100", "kroB150",
+    "kroB200", "kroC100", "kroD100", "kroE100", "lin105", "lin318", "nrw1379",
+    "p654", "pcb1173", "pcb442", "pr1002", "pr107", "pr124", "pr136", "pr144",
+    "pr152", "pr226", "pr264", "pr299", "pr439", "pr76", "rat195", "rat575",
+    "rat783", "rat99", "rd100", "rd400", "rl1304", "rl1323", "rl1889",
+    "si1032", "si175", "si535", "st70", "swiss42", "ts225", "tsp225", "u1060",
+    "u1432", "u159", "u1817", "u574", "u724", "ulysses16", "ulysses22",
+    "vm1084", "vm1748")
+
+#: The set of asymmetric instances
+_ASYMMETRIC_INSTANCES: Final[tuple[str, ...]] = (
+    "br17", "ft53", "ft70", "ftv170", "ftv33", "ftv35", "ftv38", "ftv44",
+    "ftv47", "ftv55", "ftv64", "ftv70", "kro124p", "p43", "rbg323", "rbg358",
+    "rbg403", "rbg443", "ry48p")
+
+#: The set of all TSP instances
+_INSTANCES: Final[tuple[str, ...]] = tuple(sorted(
+    _SYMMETRIC_INSTANCES + _ASYMMETRIC_INSTANCES))
+
+#: the set of asymmetric resources
+_ASYMMETRIC_RESOURCES: Final[set[str]] = set(_ASYMMETRIC_INSTANCES)
+
+#: the problem is a symmetric tsp
+_TYPE_SYMMETRIC_TSP: Final[str] = "TSP"
+#: the problem is an asymmetric tsp
+_TYPE_ASYMMETRIC_TSP: Final[str] = "ATSP"
+#: the permitted types
+_TYPES: Final[set[str]] = {_TYPE_SYMMETRIC_TSP, _TYPE_ASYMMETRIC_TSP}
+#: the name start
+_KEY_NAME: Final[str] = "NAME"
+#: the type start
+_KEY_TYPE: Final[str] = "TYPE"
+#: the dimension start
+_KEY_DIMENSION: Final[str] = "DIMENSION"
+#: the comment start
+_KEY_COMMENT: Final[str] = "COMMENT"
+#: EUC_2D coordinates
+__EWT_EUC_2D: Final[str] = "EUC_2D"
+#: geographical coordinates
+__EWT_GEO: Final[str] = "GEO"
+#: ATT coordinates
+__EWT_ATT: Final[str] = "ATT"
+#: ceiling 2D coordinates
+__EWT_CEIL2D: Final[str] = "CEIL_2D"
+#: the explicit edge weight type
+_EWT_EXPLICIT: Final[str] = "EXPLICIT"
+#: the edge weight type start
+_KEY_EDGE_WEIGHT_TYPE: Final[str] = "EDGE_WEIGHT_TYPE"
+#: the permitted edge weight types
+_EDGE_WEIGHT_TYPES: Final[set[str]] = {
+    __EWT_EUC_2D, __EWT_GEO, __EWT_ATT, __EWT_CEIL2D, _EWT_EXPLICIT}
+#: the edge weight format "function"
+__EWF_FUNCTION: Final[str] = "FUNCTION"
+#: the full matrix edge weight format
+_EWF_FULL_MATRIX: Final[str] = "FULL_MATRIX"
+#: the upper row edge weight format
+_EWF_UPPER_ROW: Final[str] = "UPPER_ROW"
+#: the lower diagonal row
+__EWF_LOWER_DIAG_ROW: Final[str] = "LOWER_DIAG_ROW"
+#: the upper diagonal row
+__EWF_UPPER_DIAG_ROW: Final[str] = "UPPER_DIAG_ROW"
+#: the edge weight format start
+_KEY_EDGE_WEIGHT_FORMAT: Final[str] = "EDGE_WEIGHT_FORMAT"
+#: the permitted edge weight formats
+_EDGE_WEIGHT_FORMATS: Final[set[str]] = {
+    __EWF_FUNCTION, _EWF_FULL_MATRIX, _EWF_UPPER_ROW,
+    __EWF_LOWER_DIAG_ROW, __EWF_UPPER_DIAG_ROW}
+#: the start of the node coord type
+_KEY_NODE_COORD_TYPE: Final[str] = "NODE_COORD_TYPE"
+#: 2d coordinates
+__NODE_COORD_TYPE_2D: Final[str] = "TWOD_COORDS"
+#: no coordinates
+__NODE_COORD_TYPE_NONE: Final[str] = "NO_COORDS"
+#: the permitted node coordinate types
+_NODE_COORD_TYPES: Final[set[str]] = {
+    __NODE_COORD_TYPE_2D, __NODE_COORD_TYPE_NONE}
+#: the node coordinate section starts
+_START_NODE_COORD_SECTION: Final[str] = "NODE_COORD_SECTION"
+#: start the edge weight section
+_START_EDGE_WEIGHT_SECTION: Final[str] = "EDGE_WEIGHT_SECTION"
+#: the end of the file
+_EOF: Final[str] = "EOF"
+#: the fixed edges section
+_FIXED_EDGES: Final[str] = "FIXED_EDGES_SECTION"
+
+
+def __line_to_nums(line: str,
+                   collector: Callable[[int | float], Any]) -> None:
+    """
+    Split a line to a list of integers.
+
+    :param line: the line string
+    :param collector: the collector
+    """
+    if not isinstance(line, str):
+        raise type_error(line, "line", str)
+    idx: int = 0
+    line = line.strip()
+    str_len: Final[int] = len(line)
+
+    while idx < str_len:
+        while (idx < str_len) and (line[idx] == " "):
+            idx += 1
+        if idx >= str_len:
+            return
+
+        next_space = line.find(" ", idx)
+        if next_space < 0:
+            next_space = str_len
+
+        part: str = line[idx:next_space]
+        if ("." in part) or ("E" in part) or ("e" in part):
+            f: float = float(part)
+            if not isfinite(f):
+                raise ValueError(
+                    f"{part!r} translates to non-finite float {f}.")
+            collector(f)
+        else:
+            collector(check_to_int_range(
+                part, "line fragment", -1_000_000_000_000, 1_000_000_000_000))
+        idx = next_space
+
+
+def __nint(v: int | float) -> int:
+    """
+    Get the nearest integer in the TSPLIB format.
+
+    :param v: the value
+    :return: the integer
+    """
+    if isinstance(v, int):
+        return v
+    if isinstance(v, float):
+        return int(0.5 + v)
+    raise type_error(v, "value", (int, float))
+
+
+def __dist_2deuc(a: list[int | float], b: list[int | float]) -> int:
+    """
+    Compute the two-dimensional Euclidean distance function.
+
+    :param a: the first point
+    :param b: the second point
+    :return: the distance
+    """
+    return __nint(sqrt(((a[0] - b[0]) ** 2) + ((a[1] - b[1]) ** 2)))
+
+
+def __matrix_from_points(
+        n_cities: int, coord_dim: int, stream: Iterable[str],
+        dist_func: Callable[[list[int | float], list[int | float]], int]) \
+        -> np.ndarray:
+    """
+    Load a distance matrix from 2D coordinates.
+
+    :param n_cities: the dimension
+    :param coord_dim: the coordinate dimension
+    :param stream: the stream
+    :param dist_func: the distance function
+    :return: the matrix
+    """
+    index: int = 0
+    coordinates: Final[list[list[int | float]]] = []
+    row: Final[list[int | float]] = []
+    for the_line in stream:
+        if not isinstance(the_line, str):
+            raise type_error(the_line, "line", str)
+        line = the_line.strip()
+        if len(line) <= 0:
+            continue
+        if line == _EOF:
+            break
+        __line_to_nums(line, row.append)
+        index += 1
+        if (len(row) != (coord_dim + 1)) or (not isinstance(row[0], int)) \
+                or (row[0] != index):
+            raise ValueError(f"invalid row {line!r} at index {index}, "
+                             f"gives values {row}.")
+        coordinates.append(row[1:])
+        row.clear()
+    if index != n_cities:
+        raise ValueError(f"only found {index} rows, but expected {n_cities}")
+
+    # now construct the matrix
+    matrix: Final[np.ndarray] = np.zeros((n_cities, n_cities),
+                                         npu.DEFAULT_INT)
+    for i in range(n_cities):
+        a = coordinates[i]
+        for j in range(i):
+            b = coordinates[j]
+            dist = dist_func(a, b)
+            if not isinstance(dist, int):
+                raise type_error(dist, f"distance[{i},{j}]", int)
+            if dist < 0:
+                raise ValueError(
+                    f"Cannot have node distance {dist}: ({a}) at index="
+                    f"{i + 1} and ({b} at index={j + 1}.")
+            matrix[i, j] = dist
+            matrix[j, i] = dist
+    return matrix
+
+
+def __coord_to_rad(x: int | float) -> float:
+    """
+    Convert coordinates to longitude/latitude.
+
+    :param x: the coordinate
+    """
+    degrees: int = int(x)
+    return (3.141592 * (degrees + (5.0 * (x - degrees)) / 3.0)) / 180.0
+
+
+def __dist_loglat(a: list[int | float], b: list[int | float]) -> int:
+    """
+    Convert a longitude-latitude pair to a distance.
+
+    :param a: the first point
+    :param b: the second point
+    :return: the distance
+    """
+    lat1: float = __coord_to_rad(a[0])
+    long1: float = __coord_to_rad(a[1])
+    lat2: float = __coord_to_rad(b[0])
+    long2: float = __coord_to_rad(b[1])
+    q1: Final[float] = cos(long1 - long2)
+    q2: Final[float] = cos(lat1 - lat2)
+    q3: Final[float] = cos(lat1 + lat2)
+    return int(6378.388 * acos(0.5 * (
+        (1.0 + q1) * q2 - (1.0 - q1) * q3)) + 1.0)
+
+
+def __dist_att(a: list[int | float], b: list[int | float]) -> int:
+    """
+    Compute the ATT Pseudo-Euclidean distance function.
+
+    :param a: the first point
+    :param b: the second point
+    :return: the distance
+    """
+    xd: Final[int | float] = a[0] - b[0]
+    yd: Final[int | float] = a[1] - b[1]
+    rij: Final[float] = sqrt((xd * xd + yd * yd) / 10.0)
+    tij: Final[int] = __nint(rij)
+    return (tij + 1) if tij < rij else tij
+
+
+def __dist_2dceil(a: list[int | float], b: list[int | float]) -> int:
+    """
+    Compute the ceiling of the two-dimensional Euclidean distance function.
+
+    :param a: the first point
+    :param b: the second point
+    :return: the distance
+    """
+    dist: Final[float] = sqrt(((a[0] - b[0]) ** 2) + ((a[1] - b[1]) ** 2))
+    disti: Final[int] = int(dist)
+    return disti if dist == disti else (disti + 1)
+
+
+def _matrix_from_node_coord_section(
+        n_cities: int | None, edge_weight_type: str | None,
+        node_coord_type: str | None, stream: Iterable[str]) -> np.ndarray:
+    """
+    Get a data matrix from a node coordinate section.
+
+    :param n_cities: the dimension
+    :param edge_weight_type: the edge weight type
+    :param node_coord_type: the node coordinate type
+    :param stream: the node coordinate stream
+    :return: the data matrix
+    """
+    check_int_range(n_cities, "n_cities", 2, 1_000_000_000_000)
+    if (node_coord_type is not None) and \
+            (not isinstance(node_coord_type, str)):
+        raise type_error(node_coord_type, "node_coord_type", str)
+    if not isinstance(edge_weight_type, str):
+        raise type_error(edge_weight_type, "edge_weight_type", str)
+
+    dist_fun = None
+    coord_dim: int | None = None
+    if (edge_weight_type == __EWT_EUC_2D) \
+            and (node_coord_type in (None, __NODE_COORD_TYPE_2D)):
+        coord_dim = 2
+        dist_fun = __dist_2deuc
+    elif (edge_weight_type == __EWT_GEO) \
+            and (node_coord_type in (None, __NODE_COORD_TYPE_2D)):
+        coord_dim = 2
+        dist_fun = __dist_loglat
+    elif (edge_weight_type == __EWT_ATT) \
+            and (node_coord_type in (None, __NODE_COORD_TYPE_2D)):
+        coord_dim = 2
+        dist_fun = __dist_att
+    elif (edge_weight_type == __EWT_CEIL2D) \
+            and (node_coord_type in (None, __NODE_COORD_TYPE_2D)):
+        coord_dim = 2
+        dist_fun = __dist_2dceil
+
+    if (coord_dim is not None) and (dist_fun is not None):
+        return __matrix_from_points(n_cities, coord_dim, stream, dist_fun)
+
+    raise ValueError(f"invalid combination of {_KEY_EDGE_WEIGHT_TYPE} "
+                     f"and {_KEY_NODE_COORD_TYPE}")
+
+
+def __read_n_ints(n: int, stream: Iterable[str]) -> list[int]:
+    """
+    Read exactly `n` integers from a stream.
+
+    :param n: the number of integers to read
+    :param stream: the stream to read from
+    :return: the list of integers
+    """
+    res: list[int] = []
+
+    def __append(i: int | float, fwd=res.append) -> None:
+        if isinstance(i, int):
+            fwd(i)
+        else:
+            i2 = int(i)
+            if i2 != i:
+                raise ValueError(f"{i} is not integer")
+            fwd(i2)
+
+    for line in stream:
+        __line_to_nums(line, __append)
+        if len(res) == n:
+            break
+
+    if len(res) != n:
+        raise ValueError(f"expected {n} integers, got {len(res)}.")
+    return res
+
+
+def _matrix_from_edge_weights(
+        n_cities: int | None, edge_weight_type: str | None,
+        edge_weight_format: str | None, stream: Iterable[str]) -> np.ndarray:
+    """
+    Get a data matrix from a edge weights section.
+
+    :param n_cities: the dimension
+    :param edge_weight_type: the edge weight type
+    :param edge_weight_format: the edge weight format
+    :param stream: the node coordinate stream
+    :return: the data matrix
+    """
+    check_int_range(n_cities, "n_cities", 2, 1_000_000_000_000)
+    if not isinstance(edge_weight_type, str):
+        raise type_error(edge_weight_type, "node_coord_type", str)
+    if not isinstance(edge_weight_type, str):
+        raise type_error(edge_weight_type, "edge_weight_type", str)
+    if edge_weight_type == _EWT_EXPLICIT:
+        if edge_weight_format == _EWF_FULL_MATRIX:
+            res = np.array(__read_n_ints(n_cities * n_cities, stream),
+                           dtype=npu.DEFAULT_INT).reshape(
+                (n_cities, n_cities))
+            np.fill_diagonal(res, 0)
+            return res
+        if edge_weight_format == _EWF_UPPER_ROW:
+            ints = __read_n_ints((n_cities * (n_cities - 1)) // 2, stream)
+            res = np.zeros((n_cities, n_cities), dtype=npu.DEFAULT_INT)
+            i: int = 1
+            j: int = 0
+            for v in ints:
+                res[j, i] = res[i, j] = v
+                i = i + 1
+                if i >= n_cities:
+                    j = j + 1
+                    i = j + 1
+            return res
+        if edge_weight_format == __EWF_LOWER_DIAG_ROW:
+            ints = __read_n_ints(
+                n_cities + ((n_cities * (n_cities - 1)) // 2), stream)
+            res = np.zeros((n_cities, n_cities), dtype=npu.DEFAULT_INT)
+            i = 0
+            j = 0
+            for v in ints:
+                if i != j:
+                    res[j, i] = res[i, j] = v
+                i = i + 1
+                if i > j:
+                    j = j + 1
+                    i = 0
+            return res
+        if edge_weight_format == __EWF_UPPER_DIAG_ROW:
+            ints = __read_n_ints(
+                n_cities + ((n_cities * (n_cities - 1)) // 2), stream)
+            res = np.zeros((n_cities, n_cities), dtype=npu.DEFAULT_INT)
+            i = 0
+            j = 0
+            for v in ints:
+                if i != j:
+                    res[j, i] = res[i, j] = v
+                i = i + 1
+                if i >= n_cities:
+                    j = j + 1
+                    i = j
+            return res
+    raise ValueError(
+        f"unsupported combination of {_KEY_EDGE_WEIGHT_TYPE}="
+        f"{edge_weight_type!r} and {_KEY_EDGE_WEIGHT_FORMAT}="
+        f"{edge_weight_format!r}")
+
+
+def _from_stream(
+        stream: Iterable[str],
+        lower_bound_getter: Callable[[str], int] | None =
+        _LOWER_BOUNDS.__getitem__) -> "Instance":
+    """
+    Read a TSP Lib instance from a TSP-lib formatted stream.
+
+    :param stream: the text stream
+    :param lower_bound_getter: a function returning a lower bound for an
+        instance name, or `None` to use a simple lower bound approximation
+    :return: the instance
+    """
+    if (lower_bound_getter is not None) \
+            and (not callable(lower_bound_getter)):
+        raise type_error(
+            lower_bound_getter, "lower_bound_getter", None, call=True)
+
+    the_name: str | None = None
+    the_type: str | None = None
+    the_n_cities: int | None = None
+    the_ewt: str | None = None
+    the_ewf: str | None = None
+    the_nct: str | None = None
+    the_matrix: np.ndarray | None = None
+
+    for the_line in stream:
+        if not isinstance(the_line, str):
+            raise type_error(the_line, "line", str)
+        line = the_line.strip()
+        if len(line) <= 0:
+            continue
+
+        sep_idx: int = line.find(":")
+        if sep_idx > 0:
+            key: str = line[:sep_idx].strip()
+            value: str = line[sep_idx + 1:].strip()
+            if len(value) <= 0:
+                raise ValueError(f"{line!r} has empty value "
+                                 f"{value!r} for key {key!r}.")
+
+            if key == _KEY_NAME:
+                if the_name is not None:
+                    raise ValueError(
+                        f"{line!r} cannot come at this position,"
+                        f" already got {_KEY_NAME}={the_name!r}.")
+                if value.endswith(".tsp"):
+                    value = value[0:-4]
+                the_name = value
+                continue
+
+            if key == _KEY_TYPE:
+                if the_type is not None:
+                    raise ValueError(
+                        f"{line!r} cannot come at this position, already "
+                        f"got {_KEY_TYPE}={the_type!r}.")
+                the_type = _TYPE_SYMMETRIC_TSP \
+                    if value == "TSP (M.~Hofmeister)" else value
+                if the_type not in _TYPES:
+                    raise ValueError(
+                        f"only {_TYPES!r} are permitted as {_KEY_TYPE}, "
+                        f"but got {the_type!r} from {line!r}.")
+                continue
+
+            if key == _KEY_DIMENSION:
+                if the_n_cities is not None:
+                    raise ValueError(
+                        f"{line!r} cannot come at this position,"
+                        f" already got {_KEY_DIMENSION}={the_n_cities}.")
+                the_n_cities = check_to_int_range(value, "dimension",
+                                                  2, 1_000_000_000)
+
+            if key == _KEY_EDGE_WEIGHT_TYPE:
+                if the_ewt is not None:
+                    raise ValueError(
+                        f"{line!r} cannot come at this position, already "
+                        f"got {_KEY_EDGE_WEIGHT_TYPE}={the_ewt!r}.")
+                the_ewt = value
+                if the_ewt not in _EDGE_WEIGHT_TYPES:
+                    raise ValueError(
+                        f"only {_EDGE_WEIGHT_TYPES!r} are permitted as "
+                        f"{_KEY_EDGE_WEIGHT_TYPE}, but got {the_ewt!r} "
+                        f"in {line!r}")
+                continue
+
+            if key == _KEY_EDGE_WEIGHT_FORMAT:
+                if the_ewf is not None:
+                    raise ValueError(
+                        f"{line!r} cannot come at this position, already "
+                        f"got {_KEY_EDGE_WEIGHT_FORMAT}={the_ewf!r}.")
+                the_ewf = value
+                if the_ewf not in _EDGE_WEIGHT_FORMATS:
+                    raise ValueError(
+                        f"only {_EDGE_WEIGHT_FORMATS!r} are permitted as "
+                        f"{_KEY_EDGE_WEIGHT_FORMAT}, but got {the_ewf} "
+                        f"in {line!r}")
+                continue
+            if key == _KEY_NODE_COORD_TYPE:
+                if the_nct is not None:
+                    raise ValueError(
+                        f"{line!r} cannot come at this position, already "
+                        f"got {_KEY_NODE_COORD_TYPE}={the_nct!r}.")
+                the_nct = value
+                if the_nct not in _NODE_COORD_TYPES:
+                    raise ValueError(
+                        f"only {_NODE_COORD_TYPES!r} are permitted as node "
+                        f"{_KEY_NODE_COORD_TYPE}, but got {the_nct!r} "
+                        f"in {line!r}")
+                continue
+        elif line == _START_NODE_COORD_SECTION:
+            if the_matrix is not None:
+                raise ValueError(
+                    f"already got matrix, cannot have {line!r} here!")
+            the_matrix = _matrix_from_node_coord_section(
+                the_n_cities, the_ewt, the_nct, stream)
+            continue
+        elif line == _START_EDGE_WEIGHT_SECTION:
+            if the_matrix is not None:
+                raise ValueError(
+                    f"already got matrix, cannot have {line!r} here!")
+            the_matrix = _matrix_from_edge_weights(
+                the_n_cities, the_ewt, the_ewf, stream)
+            continue
+        elif line == _EOF:
+            break
+        elif line == _FIXED_EDGES:
+            raise ValueError(f"{_FIXED_EDGES!r} not supported")
+
+    if the_name is None:
+        raise ValueError("did not find any name.")
+    if the_matrix is None:
+        raise ValueError(f"did not find any matrix for {the_name!r}.")
+
+    inst: Final[Instance] = Instance(
+        the_name, 0 if (lower_bound_getter is None) else
+        lower_bound_getter(the_name), the_matrix)
+    if (the_type == _TYPE_SYMMETRIC_TSP) and (not inst.is_symmetric):
+        raise ValueError("found asymmetric TSP instance but expected "
+                         f"{the_name!r} to be a symmetric one?")
+
+    return inst
+
+
+
+[docs] +def ncities_from_tsplib_name(name: str) -> int: + """ + Compute the instance scale from the instance name. + + :param name: the instance name + :return: the instance scale + """ + if name == "kro124p": + return 100 + if name == "ry48p": + return 48 + idx: int = len(name) + while name[idx - 1] in "0123456789": + idx -= 1 + scale: Final[int] = int(name[idx:]) + if name.startswith("ftv"): + return scale + 1 + return scale
+ + + +
+[docs] +class Instance(Component, np.ndarray): + """An instance of the Traveling Salesperson Problem.""" + + #: the name of the instance + name: str + #: the number of cities + n_cities: int + #: the lower bound of the tour length + tour_length_lower_bound: int + #: the upper bound of the tour length + tour_length_upper_bound: int + #: is the TSP instance symmetric? + is_symmetric: bool + + def __new__(cls, name: str, tour_length_lower_bound: int, + matrix: np.ndarray, + upper_bound_range_multiplier: int = 1) -> "Instance": + """ + Create an instance of the Traveling Salesperson Problem. + + :param cls: the class + :param name: the name of the instance + :param tour_length_lower_bound: the lower bound of the tour length + :param matrix: the matrix with the data (will be copied) + :param upper_bound_range_multiplier: a multiplier for the upper bound + to fix the data range, by default `1` + """ + use_name: Final[str] = sanitize_name(name) + if name != use_name: + raise ValueError(f"Name {name!r} is not a valid name.") + + check_int_range(tour_length_lower_bound, "tour_length_lower_bound", + 0, 1_000_000_000_000_000) + + n_cities: int = len(matrix) + if n_cities <= 1: + raise ValueError(f"There must be at least two cities in a TSP " + f"instance, but we got {n_cities}.") + + use_shape: Final[tuple[int, int]] = (n_cities, n_cities) + if isinstance(matrix, np.ndarray): + if matrix.shape != use_shape: + raise ValueError( + f"Unexpected shape {matrix.shape} for {n_cities} cities, " + f"expected {use_shape}.") + else: + raise type_error(matrix, "matrix", np.ndarray) + + # validate the matrix and compute the upper bound + upper_bound: int = 0 + lower_bound_2: int = 0 + is_symmetric: bool = True + for i in range(n_cities): + farthest_neighbor: int = -1 + nearest_neighbor: int = 9_223_372_036_854_775_807 + + for j in range(n_cities): + dist = int(matrix[i, j]) + if i == j: + if dist != 0: + raise ValueError( + f"if i=j={i}, then dist must be zero " + f"but is {dist}.") + continue + farthest_neighbor = max(farthest_neighbor, dist) + nearest_neighbor = min(nearest_neighbor, dist) + if dist != matrix[j, i]: + is_symmetric = False + if farthest_neighbor <= 0: + raise ValueError(f"farthest neighbor distance of node {i} is" + f" {farthest_neighbor}?") + upper_bound = upper_bound + farthest_neighbor + lower_bound_2 = lower_bound_2 + nearest_neighbor + + tour_length_lower_bound = max( + tour_length_lower_bound, check_int_range( + lower_bound_2, "lower_bound_2", 0, 1_000_000_000_000_000)) + check_int_range(upper_bound, "upper_bound", + tour_length_lower_bound, 1_000_000_000_000_001) + + # create the object and ensure that the backing integer type is + # large enough to accommodate all possible tour lengths + limit: Final[int] = (check_int_range( + upper_bound_range_multiplier, "upper_bound_range_multiplier", 1) + * max(upper_bound, n_cities)) + obj: Final[Instance] = super().__new__( + cls, use_shape, int_range_to_dtype( + min_value=-limit, max_value=limit)) + np.copyto(obj, matrix, "unsafe") + for i in range(n_cities): + for j in range(n_cities): + if obj[i, j] != matrix[i, j]: + raise ValueError( + f"error when copying: {obj[i, j]} != {matrix[i, j]}") + + #: the name of the instance + obj.name = use_name + #: the number of cities + obj.n_cities = n_cities + #: the lower bound of the tour length + obj.tour_length_lower_bound = tour_length_lower_bound + #: the upper bound of the tour length + obj.tour_length_upper_bound = upper_bound + #: is this instance symmetric? + obj.is_symmetric = is_symmetric + return obj + + def __str__(self): + """ + Get the name of this instance. + + :return: the name of this instance + + >>> str(Instance.from_resource("gr17")) + 'gr17' + """ + return self.name + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log the parameters of the instance to the given logger. + + :param logger: the logger for the parameters + + >>> from moptipy.utils.logger import InMemoryLogger + >>> with InMemoryLogger() as l: + ... with l.key_values("I") as kv: + ... Instance.from_resource("kroE100").log_parameters_to(kv) + ... print(repr('@'.join(l.get_log()))) + 'BEGIN_I@name: kroE100@class: moptipyapps.tsp.instance.Instance\ +@nCities: 100@tourLengthLowerBound: 22068@tourLengthUpperBound: 330799@\ +symmetric: T@dtype: i@END_I' + """ + super().log_parameters_to(logger) + logger.key_value("nCities", self.n_cities) + logger.key_value("tourLengthLowerBound", self.tour_length_lower_bound) + logger.key_value("tourLengthUpperBound", self.tour_length_upper_bound) + logger.key_value("symmetric", self.is_symmetric) + logger.key_value(npu.KEY_NUMPY_TYPE, self.dtype.char)
+ + +
+[docs] + @staticmethod + def from_file( + path: str, + lower_bound_getter: Callable[[str], int] | None = + _LOWER_BOUNDS.__getitem__) -> "Instance": + """ + Read a TSP Lib instance from a TSP-lib formatted file. + + :param path: the path to the file + :param lower_bound_getter: a function returning a lower bound for an + instance name, or `None` to use a simple lower bound approximation + :return: the instance + + >>> from os.path import dirname + >>> inst = Instance.from_file(dirname(__file__) + "/tsplib/br17.atsp") + >>> inst.name + 'br17' + """ + file: Final[Path] = file_path(path) + with file.open_for_read() as stream: + try: + return _from_stream( + cast(TextIO, stream), lower_bound_getter) + except (TypeError, ValueError) as err: + raise ValueError(f"error when parsing file {file!r}") from err
+ + +
+[docs] + @staticmethod + def from_resource(name: str) -> "Instance": + """ + Load a TSP instance from a resource. + + :param name: the name string + :return: the instance + + >>> Instance.from_resource("br17").n_cities + 17 + """ + if not isinstance(name, str): + raise type_error(name, "name", str) + container: Final = Instance.from_resource + inst_attr: Final[str] = f"__inst_{name}" + if hasattr(container, inst_attr): # instance loaded? + return cast(Instance, getattr(container, inst_attr)) + + is_symmetric: Final[bool] = name not in _ASYMMETRIC_INSTANCES + suffix: Final[str] = ".tsp" if is_symmetric else ".atsp" + with open_resource_stream(f"{name}{suffix}") as stream: + inst: Final[Instance] = _from_stream(stream) + + if inst.name != name: + raise ValueError(f"got {inst.name!r} for instance {name!r}?") + sc: int = ncities_from_tsplib_name(name) + if sc != inst.n_cities: + raise ValueError(f"expected to get n_cities={sc} from name " + f"{name!r} but got {inst.n_cities}.") + if is_symmetric and (not inst.is_symmetric): + raise ValueError(f"{name!r} should be symmetric but is not?") + if inst.n_cities <= 1000: + setattr(container, inst_attr, inst) + return inst
+ + +
+[docs] + @staticmethod + def list_resources(symmetric: bool = True, + asymmetric: bool = True) -> tuple[str, ...]: + """ + Get a tuple of all the TSP instances available as resource. + + :param symmetric: include the symmetric instances + :param asymmetric: include the asymmetric instances + :return: the tuple with the instance names + + >>> a = len(Instance.list_resources(True, True)) + >>> print(a) + 111 + >>> b = len(Instance.list_resources(True, False)) + >>> print(b) + 92 + >>> c = len(Instance.list_resources(False, True)) + >>> print(c) + 19 + >>> print(a == (b + c)) + True + >>> print(len(Instance.list_resources(False, False))) + 0 + """ + return _INSTANCES if (symmetric and asymmetric) else ( + _SYMMETRIC_INSTANCES if symmetric else ( + _ASYMMETRIC_INSTANCES if asymmetric else ()))
+ + +
+[docs] + def to_stream(self, collector: Callable[[str], Any], + comments: Iterable[str] = ()) -> None: + """ + Convert this instance to a stream. + + :param collector: the string collector + :param comments: a stream of comments + + >>> orig = Instance.from_resource("br17") + >>> text = [] + >>> orig.to_stream(text.append) + >>> reload = _from_stream(iter(text), + ... lambda _: orig.tour_length_lower_bound) + >>> orig.n_cities == reload.n_cities + True + >>> orig.name == reload.name + True + >>> list(orig.flatten()) == list(reload.flatten()) + True + + >>> orig = Instance.from_resource("att48") + >>> text = [] + >>> orig.to_stream(text.append) + >>> reload = _from_stream(iter(text), + ... lambda _: orig.tour_length_lower_bound) + >>> orig.n_cities == reload.n_cities + True + >>> orig.name == reload.name + True + >>> list(orig.flatten()) == list(reload.flatten()) + True + + >>> orig = Instance.from_resource("berlin52") + >>> text = [] + >>> orig.to_stream(text.append) + >>> reload = _from_stream(iter(text), + ... lambda _: orig.tour_length_lower_bound) + >>> orig.n_cities == reload.n_cities + True + >>> orig.name == reload.name + True + >>> list(orig.flatten()) == list(reload.flatten()) + True + + >>> orig = Instance.from_resource("ft53") + >>> text = [] + >>> orig.to_stream(text.append) + >>> reload = _from_stream(iter(text), + ... lambda _: orig.tour_length_lower_bound) + >>> orig.n_cities == reload.n_cities + True + >>> orig.name == reload.name + True + >>> list(orig.flatten()) == list(reload.flatten()) + True + """ + if not callable(collector): + raise type_error(collector, "collector", call=True) + if not isinstance(comments, Iterable): + raise type_error(comments, "comments", Iterable) + collector(f"{_KEY_NAME}: {self.name}") + t: str = _TYPE_SYMMETRIC_TSP if self.is_symmetric \ + else _TYPE_ASYMMETRIC_TSP + collector(f"{_KEY_TYPE}: {t}") + for comment in comments: + collector(f"{_KEY_COMMENT}: {str.strip(comment)}") + collector(f"{_KEY_DIMENSION}: {self.n_cities}") + collector(f"{_KEY_EDGE_WEIGHT_TYPE}: {_EWT_EXPLICIT}") + t = _EWF_UPPER_ROW if self.is_symmetric else _EWF_FULL_MATRIX + collector(f"{_KEY_EDGE_WEIGHT_FORMAT}: {t}") + collector(_START_EDGE_WEIGHT_SECTION) + + if self.is_symmetric: + for i in range(self.n_cities): + collector(" ".join(map(str, list(self[i][i + 1:])))) + else: + for i in range(self.n_cities): + collector(" ".join(map(str, self[i]))) + collector(_EOF)
+
+ +
diff --git a/_modules/moptipyapps/tsp/known_optima.html b/_modules/moptipyapps/tsp/known_optima.html new file mode 100644 index 00000000..5fb3cf22 --- /dev/null +++ b/_modules/moptipyapps/tsp/known_optima.html @@ -0,0 +1,141 @@ +moptipyapps.tsp.known_optima — moptipyapps 0.8.62 documentation

Source code for moptipyapps.tsp.known_optima

+"""
+A set of tools to load known optima in the TSPLib format.
+
+The TSPLib provides benchmark instances for the Traveling Salesperson
+Problem (TSP). We provide a subset of these instances with up to 2000 cities
+as resources in :mod:`~moptipyapps.tsp.instance`. Additionally, TSPLib
+provides the optimal solutions for a subset of these instances. Within this
+module here, we provide methods for reading the optimal solution files
+in the TSPLib format. We also include as resources the optimal solutions to
+the instances that our package provide as resources as well.
+
+You can get the list of instance names for which the optimal tours are
+included in this package via :func:`list_resource_tours` and then load a tour
+from a resource via :func:`opt_tour_from_resource`. If you want to read a tour
+from an external file that obeys the TSPLib format, you can do so via
+:func:`opt_tour_from_file`.
+
+1. Gerhard Reinelt. TSPLIB - A Traveling Salesman Problem Library.
+   *ORSA Journal on Computing* 3(4):376-384. November 1991.
+   https://doi.org/10.1287/ijoc.3.4.376.
+   http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/
+2. Gerhard Reinelt. *TSPLIB95.* Heidelberg, Germany: Universität
+   Heidelberg, Institut für Angewandte Mathematik. 1995.
+   http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp95.pdf
+"""
+
+from typing import Final, TextIO, cast
+
+import moptipy.utils.nputils as npu
+import numpy as np
+from pycommons.io.path import Path, file_path
+from pycommons.types import check_to_int_range, type_error
+
+from moptipyapps.tsp.tsplib import open_resource_stream
+
+#: the set of known optimal tours
+_TOURS: Final[tuple[str, ...]] = (
+    "a280", "att48", "bayg29", "bays29", "berlin52", "brg180", "ch130",
+    "ch150", "cn11", "eil101", "eil51", "eil76", "fri26", "gr120", "gr202",
+    "gr24", "gr48", "gr666", "gr96", "kroA100", "kroC100", "kroD100",
+    "lin105", "pcb442", "pr1002", "pr76", "rd100", "st70", "tsp225",
+    "ulysses16", "ulysses22")
+
+
+def _from_stream(stream: TextIO) -> np.ndarray:
+    """
+    Read an optimal tour from a stream.
+
+    :param stream: the text stream
+    :return: the tour
+    """
+    nodes: list[int] = []
+    in_tour: bool = False
+    done_nodes: set[int] = set()
+    max_node: int = -1
+
+    for the_line in stream:
+        if not isinstance(the_line, str):
+            raise type_error(the_line, "line", str)
+        line = the_line.strip().upper()
+        if len(line) <= 0:
+            continue
+        if line == "TOUR_SECTION":
+            in_tour = True
+            continue
+        if line in ("-1", "EOF"):
+            break
+        if in_tour:
+            for node_str in line.rsplit():
+                node = check_to_int_range(
+                    node_str, "node", 1, 1_000_000_000_000)
+                if node in done_nodes:
+                    raise ValueError(f"encountered node {node} twice")
+                done_nodes.add(node)
+                max_node = max(max_node, node)
+                nodes.append(node - 1)
+    if len(nodes) != max_node:
+        raise ValueError(
+            f"expected {max_node} nodes, got {len(nodes)} instead.")
+    return np.array(nodes, npu.int_range_to_dtype(0, max_node - 1))
+
+
+
+[docs] +def opt_tour_from_file(path: str) -> np.ndarray: + """ + Read a known optimal tour from a file. + + :param path: the path to the file + :return: the tour + """ + file: Final[Path] = file_path(path) + with file.open_for_read() as stream: + try: + return _from_stream(cast(TextIO, stream)) + except (TypeError, ValueError) as err: + raise ValueError(f"error when parsing file {file!r}") from err
+ + + +
+[docs] +def opt_tour_from_resource(name: str) -> np.ndarray: + """ + Load an optimal tour from a resource. + + >>> np.array2string(opt_tour_from_resource("ulysses16")) + '[ 0 13 12 11 6 5 14 4 10 8 9 15 2 1 3 7]' + + >>> np.array2string(opt_tour_from_resource("cn11")) + '[ 0 2 3 6 9 10 5 8 7 1 4]' + + :param name: the name string + :return: the optimal tour + """ + if not isinstance(name, str): + raise type_error(name, "name", str) + with open_resource_stream(f"{name}.opt.tour") as stream: + return _from_stream(stream)
+ + + +
+[docs] +def list_resource_tours() -> tuple[str, ...]: + """ + Get a tuple of the names of the optimal tours in the resources. + + >>> list_resource_tours() + ('a280', 'att48', 'bayg29', 'bays29', 'berlin52', 'brg180', 'ch130', \ +'ch150', 'cn11', 'eil101', 'eil51', 'eil76', 'fri26', 'gr120', 'gr202', \ +'gr24', 'gr48', 'gr666', 'gr96', 'kroA100', 'kroC100', 'kroD100', 'lin105', \ +'pcb442', 'pr1002', 'pr76', 'rd100', 'st70', 'tsp225', 'ulysses16', \ +'ulysses22') + + :return: the tuple + """ + return _TOURS
+ +
diff --git a/_modules/moptipyapps/tsp/tour_length.html b/_modules/moptipyapps/tsp/tour_length.html new file mode 100644 index 00000000..e6f70b4a --- /dev/null +++ b/_modules/moptipyapps/tsp/tour_length.html @@ -0,0 +1,176 @@ +moptipyapps.tsp.tour_length — moptipyapps 0.8.62 documentation

Source code for moptipyapps.tsp.tour_length

+"""
+The tour length objective function for tours in path representation.
+
+A Traveling Salesperson Problem (TSP) instance  is defined as a
+fully-connected graph with
+:attr:`~moptipyapps.tsp.instance.Instance.n_cities` nodes. Each edge in the
+graph has a weight, which identifies the distance between the nodes. The goal
+is to find the *shortest* tour that visits every single node in the graph
+exactly once and then returns back to its starting node. Then nodes are
+usually called cities. In this file, we present methods for loading instances
+of the TSP as distance matrices `A`. In other words, the value at `A[i, j]`
+identifies the travel distance from `i` to `j`.
+
+A tour can be represented in path representation, which means that it is
+stored as a permutation of the numbers `0` to `n_cities-1`. The number at
+index `k` identifies that `k`-th city to visit. So the first number in the
+permutation identifies the first city, the second number the second city,
+and so on.
+
+The length of the tour can be computed by summing up the distances from the
+`k`-th city to the `k+1`-st city, for `k` in `0..n_cities-2` and then adding
+the distance from the last city to the first city. This is what the function
+:func:`tour_length` is doing. This function is then wrapped as objective
+function object in :class:`TourLength`.
+
+Important initial work on this code has been contributed by Mr. Tianyu LIANG
+(梁天宇), <liangty@stu.hfuu.edu.cn> a Master's student at the Institute of
+Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School
+of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei
+University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the
+supervision of Prof. Dr. Thomas Weise (汤卫思教授).
+
+1. Gerhard Reinelt. TSPLIB - A Traveling Salesman Problem Library.
+   *ORSA Journal on Computing* 3(4):376-384. November 1991.
+   https://doi.org/10.1287/ijoc.3.4.376.
+   http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/
+2. Gerhard Reinelt. *TSPLIB95.* Heidelberg, Germany: Universität
+   Heidelberg, Institut für Angewandte Mathematik. 1995.
+   http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp95.pdf
+3. David Lee Applegate, Robert E. Bixby, Vašek Chvátal, and William John Cook.
+   *The Traveling Salesman Problem: A Computational Study.* Second Edition,
+   2007. Princeton, NJ, USA: Princeton University Press. Volume 17 of
+   Princeton Series in Applied Mathematics. ISBN: 0-691-12993-2.
+4. Gregory Z. Gutin and Abraham P. Punnen. *The Traveling Salesman Problem and
+   its Variations.* 2002. Kluwer Academic Publishers. Volume 12 of
+   Combinatorial Optimization. https://doi.org/10.1007/b101971.
+5. Pedro Larrañaga, Cindy M. H. Kuijpers, Roberto H. Murga, Iñaki Inza, and
+   Sejla Dizdarevic. Genetic Algorithms for the Travelling Salesman Problem: A
+   Review of Representations and Operators. *Artificial Intelligence Review*
+   13(2):129-170. April 1999. https://doi.org/10.1023/A:1006529012972.
+6. Eugene Leighton Lawler, Jan Karel Lenstra, Alexander Hendrik George Rinnooy
+   Kan, and David B. Shmoys. *The Traveling Salesman Problem: A Guided Tour of
+   Combinatorial Optimization.* September 1985. Chichester, West Sussex, UK:
+   Wiley Interscience. In Estimation, Simulation, and
+   Control - Wiley-Interscience Series in Discrete Mathematics and
+   Optimization. ISBN: 0-471-90413-9
+7. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, and Thomas Weise.
+   Solving the Traveling Salesperson Problem using Frequency Fitness
+   Assignment. In *Proceedings of the IEEE Symposium on Foundations of
+   Computational Intelligence (IEEE FOCI'22),* part of the *IEEE Symposium
+   Series on Computational Intelligence (SSCI'22),* December 4-7, 2022,
+   Singapore. Pages 360-367. IEEE.
+   https://doi.org/10.1109/SSCI51031.2022.10022296.
+8. Thomas Weise, Raymond Chiong, Ke Tang, Jörg Lässig, Shigeyoshi Tsutsui,
+   Wenxiang Chen, Zbigniew Michalewicz, and Xin Yao. Benchmarking Optimization
+   Algorithms: An Open Source Framework for the Traveling Salesman Problem.
+   *IEEE Computational Intelligence Magazine.* 9(3):40-52. August 2014.
+   https://doi.org/10.1109/MCI.2014.2326101.
+"""
+
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.api.objective import Objective
+from moptipy.utils.logger import KeyValueLogSection
+from pycommons.types import type_error
+
+from moptipyapps.shared import SCOPE_INSTANCE
+from moptipyapps.tsp.instance import Instance
+
+
+
+[docs] +@numba.njit(cache=True, inline="always", fastmath=False, boundscheck=False) +def tour_length(instance: np.ndarray, x: np.ndarray) -> int: + """ + Compute the length of a tour. + + :param instance: the distance matrix + :param x: the tour + :return: the length of the tour `x` + """ + result: int = 0 + last: int = x[-1] + for cur in x: + result = result + instance[last, cur] + last = cur + return result
+ + + +
+[docs] +class TourLength(Objective): + """The tour length objective function.""" + + def __init__(self, instance: Instance) -> None: + """ + Initialize the tour length objective function. + + :param instance: the tsp instance + """ + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + #: the TSP instance we are trying to solve + self.instance: Final[Instance] = instance + +
+[docs] + def evaluate(self, x) -> int: + """ + Compute the length of a tour in path representation. + + :param x: the tour in path representation + :return: the tour length + """ + return tour_length(self.instance, x)
+ + +
+[docs] + def lower_bound(self) -> int: + """ + Get the lower bound of the tour length. + + :return: the lower bound of the tour length + """ + return self.instance.tour_length_lower_bound
+ + +
+[docs] + def upper_bound(self) -> int: + """ + Get the upper bound of the tour length. + + :return: the upper bound of the tour length + """ + return self.instance.tour_length_upper_bound
+ + + def __str__(self): + """ + Get the name of this objective function: always "tourLength". + + :return: "tourLength" + :retval "tourLength": always + """ + return "tourLength" + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log the parameters of the space to the given logger. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + with logger.scope(SCOPE_INSTANCE) as kv: + self.instance.log_parameters_to(kv)
+
+ +
diff --git a/_modules/moptipyapps/tsp/tsplib.html b/_modules/moptipyapps/tsp/tsplib.html new file mode 100644 index 00000000..2ec92ca4 --- /dev/null +++ b/_modules/moptipyapps/tsp/tsplib.html @@ -0,0 +1,43 @@ +moptipyapps.tsp.tsplib — moptipyapps 0.8.62 documentation

Source code for moptipyapps.tsp.tsplib

+"""
+The TSPLib example data for the Traveling Salesperson Problem (TSP).
+
+This package does not offer anything useful except for holding the TSPLib
+files. You can find the documentation and actual classes for solving and
+playing around with the TSP in package :mod:`~moptipyapps.tsp`.
+
+The original data of TSPLib can be found at
+<http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/>. Before doing
+anything with these data directly, you should make sure to read the FAQ
+<http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/TSPFAQ.html> and the
+documentation
+<http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp95.pdf>.
+
+1. Gerhard Reinelt. TSPLIB - A Traveling Salesman Problem Library.
+   *ORSA Journal on Computing* 3(4):376-384. November 1991.
+   https://doi.org/10.1287/ijoc.3.4.376.
+   http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/
+2. Gerhard Reinelt. *TSPLIB95.* Heidelberg, Germany: Universität
+   Heidelberg, Institut für Angewandte Mathematik. 1995.
+   http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp95.pdf
+"""
+
+from importlib import resources  # nosem
+from typing import TextIO, cast
+
+from pycommons.io.path import UTF8
+
+
+
+[docs] +def open_resource_stream(file_name: str) -> TextIO: + """ + Open a TSPLib resource stream. + + :param file_name: the file name of the resource + :return: the stream + """ + return cast(TextIO, resources.files(__package__).joinpath( + file_name).open("r", encoding=UTF8))
+ +
diff --git a/_modules/moptipyapps/ttp/.nojekyll b/_modules/moptipyapps/ttp/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_modules/moptipyapps/ttp/errors.html b/_modules/moptipyapps/ttp/errors.html new file mode 100644 index 00000000..84c071d9 --- /dev/null +++ b/_modules/moptipyapps/ttp/errors.html @@ -0,0 +1,388 @@ +moptipyapps.ttp.errors — moptipyapps 0.8.62 documentation

Source code for moptipyapps.ttp.errors

+"""
+An objective that counts constraint violations.
+
+The idea is that we will probably not be able to always produce game plans
+that adhere to all the constraints imposed by a Traveling Tournament Problem
+:mod:`~moptipyapps.ttp` :mod:`~moptipyapps.ttp.instance`, so we will instead
+probably usually generate game plans that may contain errors.
+
+We will hope that optimization can take care of this by applying this
+objective function here to get rid of them. In the documentation of function
+:func:`~moptipyapps.ttp.errors.count_errors`, we explain the different types
+of errors that may occur and that are counted.
+
+This objective function plays thus well with encodings that produce infeasible
+schedules, such as the very simple :mod:`~moptipyapps.ttp.game_encoding`.
+"""
+
+
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.api.objective import Objective
+from moptipy.utils.nputils import int_range_to_dtype
+from pycommons.types import type_error
+
+from moptipyapps.ttp.game_plan import GamePlan
+from moptipyapps.ttp.instance import Instance
+
+
+
+[docs] +@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) +def count_errors(y: np.ndarray, home_streak_min: int, + home_streak_max: int, away_streak_min: int, + away_streak_max: int, separation_min: int, + separation_max: int, temp_1: np.ndarray, + temp_2: np.ndarray) -> int: + """ + Compute the number of errors in a game plan. + + This method counts the total number of the violations of any of the + following constraints over `D = (n - 1) * rounds` days for `n`-team + tournaments, where `rounds == 2` for double-round robin. The following + kinds of errors are counted: + + 1. If a team `A` plays a team `B` on a given day, then `B` must play + against `A` on that day and if `A` plays at home, then `B` must play + away. If not, then that's an error. This can result in at most + `D * n` errors, because each of the `n` teams has (at most) one game + on each of the `D` days and if the *other* team could play against + the wrong team (or does not play at all), then that's one error. + 2. If a team has no other team assigned to play with, this is designated + by value `0` and causes 1 error. This error also ends all ongoing + streaks, i.e., may additionally lead to a streak violation of at + most `max(home_streak_min, away_streak_min) - 1`. However, this + cannot be more then two errors in sum per day (minus 1, for the + first day). Also, this error is mutually exclusive with error 1. + This can result in at most `D * n + (D - 1) * n = (2*D - 1) * n` + errors. Since this error is also mutually exclusive with the errors + from constraints 3 to 8 below, this gives us an upper bound of + `(2*D - 1) * n` errors for all of the constraints (1-8) together. + 3. A team has a home streak shorter than `home_streak_min`. No such error + can occur on the first day. This error is mutually exclusive with + error 2, as the streak violation is already counted there. + This can result in at most `(D - 1) * n` errors, but this number is + shared with the following constraints (4-6), because a streak can only + either be a home streak or an away streak but not both and it can + either be too short or too long, but not both. + 4. A team has a home streak longer than `home_streak_max`. No such error + can occur on the first day. This error is mutually exclusive with + error 2. + 5. A team has an away streak shorter than `away_streak_min`. No such + error can occur on the first day. This error is mutually exclusive + with error 2, as the streak violation is already counted there. + 6. A team has an away streak longer than `away_streak_max`. No such + error can occur on the first day. This error is mutually exclusive + with error 2. + 7. Team `A` plays team `B` *again* before the team has played at least + `separation_min` games against other teams. This error cannot occur + on the first day and is mutually exclusive with error 2. + There can be most 1 such error per day for any pairing of teams and + there are `n//2` pairings per day, giving us an upper bound of + `D * n//2` errors in total. This error is mutually exclusive with + the next constraint 8 (and constraint 2). + 8. Team `A` plays team `B` *again* after the team has played more than + `separation_max` games against other teams. This error cannot occur + on the first day and is mutually exclusive with error 2. + There can be most 1 such error per day for any pairing of teams and + there are `n//2` pairings per day, giving us an upper bound of + `D * n//2` errors in total. This error is mutually exclusive with + the previous constraint 7 (and constraint 2). + 9. If team `A` plays team `B` at home `a` times, then team `B` must play + team `A` at home at least `a-1` and at most `a+1` times. + In total, we have `D*n` games. There cannot be more than `(D*n) - 1` + such errors. Notice that this kind of error can never occur if we use + our :mod:`~moptipyapps.ttp.game_encoding` as representation. + 10. Each pairing of teams occurs as same as often, namely `rounds` times, + with `rounds = 2` for double-round robin. + In total, we have `D*n` games. There cannot be more than `D*n` such + errors. Notice that this kind of error can never occur if we use + our :mod:`~moptipyapps.ttp.game_encoding` as representation. + + The violations are counted on a per-day basis. For example, if + `home_streak_max` is `3` and a team has a home streak of length `5`, then + this counts as `2` errors. However, the errors are also counted in a + non-redundant fashion: If a team `A` violates `separation_min` by, say, + two days, then this counts as two errors. However, in this case, its + opposing team `B` would have necessarily incured the exactly same + violations. These are then not counted. + + As upper bound for the number of errors, we therefore have to add those of + constraints 2, 9, and 10 and get `(2*D - 1) * n + D*n - 1 + D*n`, which + gives us `(4*D - 1) * n - 1, where `D = (n - 1) * rounds`. + The lower bound is obviously `0`. + + :param y: the game plan + :param home_streak_min: the minimum permitted home streak length + :param home_streak_max: the maximum permitted home streak length + :param away_streak_min: the minimum permitted away streak length + :param away_streak_max: the maximum permitted away streak length + :param separation_min: the minimum number of games between a repetition + :param separation_max: the maximum number games between a repetition + :param temp_1: a temporary `n*(n-1)/2` integer array, which is used to + hold, for each pairing, when the last game was played + :param temp_2: a temporary `n,n` integer array, which is used to hold, + how often each team played each other team + :returns: the total number of errors. `0` if the game plan is feasible + + >>> count_errors(np.array([[-2, 1], [2, -1]], int), + ... 1, 3, 1, 3, 1, 2, np.empty(1, int), + ... np.empty((2, 2), int)) + 1 + >>> count_errors(np.array([[2, -1], [-2, 1]], int), + ... 1, 3, 1, 3, 1, 2, np.empty(1, int), + ... np.empty((2, 2), int)) + 1 + >>> count_errors(np.array([[ 2, -1, 4, -3], + ... [ 4, 3, -2, -1], + ... [-2, 1, -4, 3], + ... [-4, -3, 2, 1], + ... [ 3, 4, -1, -2], + ... [-3, -4, 1, 2]], int), + ... 1, 3, 1, 3, 1, 2, np.empty(6, int), + ... np.empty((4, 4), int)) + 2 + >>> count_errors(np.array([[ 2, -1, 4, -3], + ... [ 4, 3, -2, -1], + ... [-2, 1, -4, 3], + ... [ 3, 4, -1, -2], + ... [-4, -3, 2, 1], + ... [-3, -4, 1, 2]], int), + ... 1, 3, 1, 3, 1, 2, np.empty(6, int), + ... np.empty((4, 4), int)) + 0 + >>> count_errors(np.array([[ 2, -1, 4, -3], + ... [ 4, 3, -2, -1], + ... [ 3, 4, -1, -2], + ... [-2, 1, -4, 3], + ... [-4, -3, 2, 1], + ... [-3, -4, 1, 2]], int), + ... 1, 3, 1, 3, 1, 2, np.empty(6, int), + ... np.empty((4, 4), int)) + 0 + >>> count_errors(np.array([[ 2, -1, 4, -3], + ... [ 4, 3, -2, -1], + ... [ 3, 4, -1, -2], + ... [-2, 1, -4, 3], + ... [-4, -3, 2, 1], + ... [-3, -4, 1, 2]], int), + ... 1, 2, 1, 3, 1, 2, np.empty(6, int), + ... np.empty((4, 4), int)) + 3 + >>> count_errors(np.array([[ 2, -1, 4, -3], + ... [ 4, 3, -2, -1], + ... [ 3, 4, -1, -2], + ... [-2, 1, -4, 3], + ... [-4, -3, 2, 1], + ... [-3, -4, 1, 2]], int), + ... 1, 2, 1, 2, 1, 2, np.empty(6, int), + ... np.empty((4, 4), int)) + 6 + >>> count_errors(np.array([[ 2, -1, 4, -3], + ... [ 4, 3, -2, -1], + ... [ 3, 4, -1, -2], + ... [-2, 1, -4, 3], + ... [-4, -3, 2, 1], + ... [-3, -4, 1, 2]], int), + ... 1, 3, 1, 3, 1, 1, np.empty(6, int), + ... np.empty((4, 4), int)) + 6 + """ + days, teams = y.shape # get the number of days and teams + errors: int = 0 # the error counter + temp_1.fill(-1) # last time the teams played each other + temp_2.fill(0) + for team_1 in range(teams): + col = y[:, team_1] + team_1_id: int = team_1 + 1 + is_in_home_streak: bool = False + home_streak_len: int = -1 + is_in_away_streak: bool = False + away_streak_len: int = -1 + + for day, team_2_id in enumerate(col): + if team_2_id == 0: # is there no game on this day? + errors += 1 # no game on this day == 1 error + + if is_in_away_streak: # this ends away streaks + is_in_away_streak = False + if away_streak_len < away_streak_min: + errors += (away_streak_min - away_streak_len) + away_streak_len = -1 + elif is_in_home_streak: # this ends home streaks, too + is_in_home_streak = False + if home_streak_len < home_streak_min: + errors += (home_streak_min - home_streak_len) + home_streak_len = -1 + continue # nothing more to do here + + if team_2_id > 0: # our team plays a home game + + # If team_2 > 0, this is a home game. So the other team + # must have the corresponding index. + team_2 = team_2_id - 1 + if y[day, team_2] != -team_1_id: + errors += 1 + # increase number of home games of team 1 against team 2 + temp_2[team_1, team_2] += 1 + + if is_in_home_streak: # if we are in a home streak... + home_streak_len += 1 # ...it continues + if home_streak_len > home_streak_max: + errors += 1 # too long? add to errors + else: # if we are not in home streak, it begins + is_in_home_streak = True + home_streak_len = 1 + # if a home streak begins, any away streak ends + if is_in_away_streak: + if away_streak_len < away_streak_min: + errors += (away_streak_min - away_streak_len) + away_streak_len = -1 + is_in_away_streak = False + else: # else: team_2 < 0 --> team_2 at home, team_1 away + team_2 = (-team_2_id) - 1 + + # This is an away game, so the other team must have the + # corresponding id + if y[day, team_2] != team_1_id: + errors += 1 + + if is_in_away_streak: # if we are in an away streak... + away_streak_len += 1 # ...the streak continues + if away_streak_len > away_streak_max: + errors += 1 # away streak too long? add to error + else: # team_1 away, but not in away streak + is_in_away_streak = True + away_streak_len = 1 + if is_in_home_streak: + if home_streak_len < home_streak_min: + errors += (home_streak_min - home_streak_len) + is_in_home_streak = False + home_streak_len = 0 + + # now we need to check for the game separation difference + idx: int = ((team_1 * (team_1 - 1) // 2) + team_2) \ + if team_1 > team_2 \ + else ((team_2 * (team_2 - 1) // 2) + team_1) + last_time: int = temp_1[idx] + if last_time >= 0: + if last_time < day: + difference = day - last_time - 1 + if difference < separation_min: + errors += (separation_min - difference) + elif difference > separation_max: + errors += (difference - separation_max) + else: + continue + temp_1[idx] = day + + # sum up the team games + games_per_combo: Final[int] = days // (teams - 1) + for i in range(teams): + for j in range(i): + ij = temp_2[i, j] + ji = temp_2[j, i] + errors += abs(ij + ji - games_per_combo) + diff = abs(ij - ji) + if diff > 1: + errors += diff - 1 + + return int(errors)
+ + + +
+[docs] +class Errors(Objective): + """ + Compute the errors in a game plan. + + This objective function encompasses all the constraints imposed on + standard TTP instances in one summarizing number. See the documentation + of :func:`count_errors` for more information. + """ + + def __init__(self, instance: Instance) -> None: + """ + Initialize the errors objective function. + + :param instance: the TTP instance + """ + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + super().__init__() + + #: the TTP instance + self.instance: Final[Instance] = instance + n: Final[int] = instance.n_cities + # the data type for the temporary arrays + dtype: Final[np.dtype] = int_range_to_dtype( + -1, (n - 1) * instance.rounds) + #: the internal temporary array 1 + self.__temp_1: Final[np.ndarray] = np.empty(n * (n - 1) // 2, dtype) + #: the internal temporary array 2 + self.__temp_2: Final[np.ndarray] = np.empty((n, n), dtype) + +
+[docs] + def evaluate(self, x: GamePlan) -> int: + """ + Count the errors in a game plan as objective value. + + :param x: the game plan + :return: the number of errors in the plan + """ + inst: Final[Instance] = x.instance + return count_errors(x, inst.home_streak_min, inst.home_streak_max, + inst.away_streak_min, inst.away_streak_max, + inst.separation_min, inst.separation_max, + self.__temp_1, self.__temp_2)
+ + +
+[docs] + def lower_bound(self) -> int: + """ + Obtain the lower bound for errors: `0`, which means error-free. + + :return: `0` + """ + return 0
+ + +
+[docs] + def upper_bound(self) -> int: + """ + Compute upper bound for errors: `(4*D - 1) * n - 1`. + + Here `D` is the number of days, `n` is the number of teams, and + `D = (n - 1) * rounds`. See the documentation of :func:`count_errors`. + + :return: `(4*D - 1) * n - 1` + """ + n: Final[int] = self.instance.n_cities + rounds: Final[int] = self.instance.rounds + days: Final[int] = (n - 1) * rounds + return (4 * days - 1) * n - 1
+ + +
+[docs] + def is_always_integer(self) -> bool: + """ + State that this objective function is always integer-valued. + + :return: `True` + """ + return True
+ + + def __str__(self) -> str: + """Get the name of this objective function.""" + return "errors"
+ +
diff --git a/_modules/moptipyapps/ttp/game_encoding.html b/_modules/moptipyapps/ttp/game_encoding.html new file mode 100644 index 00000000..c87f71ee --- /dev/null +++ b/_modules/moptipyapps/ttp/game_encoding.html @@ -0,0 +1,245 @@ +moptipyapps.ttp.game_encoding — moptipyapps 0.8.62 documentation

Source code for moptipyapps.ttp.game_encoding

+"""
+A permutation-with-repetition-based encoding based on games.
+
+A point in the search space is a permutation (potentially with repetitions)
+that can be translated to a :class:`~moptipyapps.ttp.game_plan.GamePlan`.
+Each value `v` in the permutation represents a game to be played by two of
+the `n` teams. There are `n(n-1)` possible games between `n` teams,
+distinguishing home and away teams. Given a value `v` from `0..n(n-1)-1`,
+we can get the zero-based index of the home team as
+`home_idx = (game // (n - 1)) % n`. The away index is computed in two steps,
+first we set `away_idx = game % (n - 1)` and if `away_idx >= home_idx`, we
+do `away_idx = away_idy + 1`. (Because a team can never play against itself,
+the situation that `home_idx == away_idx` does not need to be represented, so
+we can "skip" over this possible value by doing the `away_idx = away_idy + 1`
+and thus get a more "compact" numeric range for the permutation elements.)
+
+A game schedule for any round-robin tournament with any given number of rounds
+can then be represented as permutation (potentially with repetitions) of these
+game values. In the decoding procedure, it is processed from beginning to end
+each game is then placed into the earliest slot not already occupied by
+another game. In other words, it is placed at the earliest day at which both
+involved teams do not yet have other games. If no such slot is available, this
+game is not placed at all. In this case, there will be some zeros in the game
+plan after the encoding. No other constraint is considered at this stage.
+
+In other words, this encoding may produce game plans that violate constraints.
+It does not care about the streak length constraints.
+It does not ensure that each team always has a game.
+Therefore, it should only be used in conjunction with objective functions that
+force the search towards feasible solutions, such as the
+:mod:`~moptipyapps.ttp.errors` objective.
+"""
+
+
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.api.encoding import Encoding
+from moptipy.spaces.permutations import Permutations
+from pycommons.types import check_int_range
+
+from moptipyapps.ttp.instance import Instance
+
+
+
+[docs] +def search_space_for_n_and_rounds(n: int, rounds: int) -> Permutations: + """ + Create a proper search space for the given number of teams and rounds. + + If the instance prescribes a double-round robin tournament, then this + is just the :meth:`~moptipy.spaces.permutations.Permutations.standard` + permutations set. Otherwise, it will be a permutation where some + elements are omitted (for + :attr:`~moptipyapps.ttp.instance.Instance.rounds` == 1) or duplicated + (if :attr:`~moptipyapps.ttp.instance.Instance.rounds` > 2). + + If an odd number of rounds is played, then it is not possible that all + teams have the same number of games at home and away. Then, the + permutation is generated such that, if the highest numbers of games at + home for any team is `k`, no other team has less than `k - 1` games at + home. If the number of rounds is even, then all teams will have the + same number of home and away games, that is, the number of teams + divided by two and multiplied by the number of rounds. + + :param n: the number of teams + :param rounds: the number of rounds + :return: the search space + + >>> ";".join(map(str, search_space_for_n_and_rounds(2, 2).blueprint)) + '0;1' + >>> ";".join(map(str, search_space_for_n_and_rounds(2, 3).blueprint)) + '0;1;1' + >>> ";".join(map(str, search_space_for_n_and_rounds(2, 4).blueprint)) + '0;0;1;1' + >>> ";".join(map(str, search_space_for_n_and_rounds(2, 5).blueprint)) + '0;0;1;1;1' + >>> ";".join(map(str, search_space_for_n_and_rounds(3, 1).blueprint)) + '1;2;5' + >>> ";".join(map(str, search_space_for_n_and_rounds(3, 2).blueprint)) + '0;1;2;3;4;5' + >>> ";".join(map(str, search_space_for_n_and_rounds(3, 3).blueprint)) + '0;1;1;2;2;3;4;5;5' + >>> ";".join(map(str, search_space_for_n_and_rounds(4, 1).blueprint)) + '1;2;3;7;8;10' + >>> ";".join(map(str, search_space_for_n_and_rounds(4, 2).blueprint)) + '0;1;2;3;4;5;6;7;8;9;10;11' + >>> ";".join(map(str, search_space_for_n_and_rounds(4, 3).blueprint)) + '0;1;1;2;2;3;3;4;5;6;7;7;8;8;9;10;10;11' + >>> ";".join(map(str, search_space_for_n_and_rounds(4, 4).blueprint)) + '0;0;1;1;2;2;3;3;4;4;5;5;6;6;7;7;8;8;9;9;10;10;11;11' + >>> ";".join(map(str, search_space_for_n_and_rounds(4, 5).blueprint)) + '0;0;1;1;1;2;2;2;3;3;3;4;4;5;5;6;6;7;7;7;8;8;8;9;9;10;10;10;11;11' + >>> ";".join(map(str, search_space_for_n_and_rounds(5, 1).blueprint)) + '1;2;4;7;9;10;13;15;16;18' + >>> ";".join(map(str, search_space_for_n_and_rounds(5, 2).blueprint)) + '0;1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;16;17;18;19' + >>> ";".join(map(str, search_space_for_n_and_rounds(5, 3).blueprint)) + '0;1;1;2;2;3;4;4;5;6;7;7;8;9;9;10;10;11;12;13;13;14;15;15;16;16;17;18;\ +18;19' + """ + check_int_range(n, "n", 2, 100000) + check_int_range(n, "rounds", 1, 100000) + div: Final[int] = n - 1 + games: Final[list[int]] = [] + order: bool = False + for r in range(rounds): + # If we have an odd round of games, the very last round needs + # to be treated differently to ensure that the home-away games + # distribution is fair. + normal: bool = (r < (rounds - 1)) or ((rounds % 2) == 0) + for i in range(n): # for each city + for j in range(i): # for each other city + order = ((r % 2) == 0) if normal else (not order) + m1 = i if order else j # determine home city + m2 = j if order else i # determine away city + if m2 > m1: + m2 -= 1 + games.append(m1 * div + m2) # add encoded game tuple + games.sort() + return Permutations(games) # create permutations with repetition
+ + + +
+[docs] +@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) +def map_games(x: np.ndarray, y: np.ndarray) -> None: + """ + Translate a permutation of games to a game plan. + + This is a straightforward decoding that places the games into the map one + by one. Each game is placed at the earliest slot in which it can be + placed. If a game cannot be placed, it is ignored. This will lead to many + errors, which can be counted via the :mod:`~moptipyapps.ttp.errors` + objective. + + :param x: the source permutation + :param y: the destination game plan + + >>> from moptipy.utils.nputils import int_range_to_dtype + >>> teams = 2 + >>> rounds = 2 + >>> perm = search_space_for_n_and_rounds(teams, rounds).blueprint + >>> dest = np.empty((rounds * (teams - 1), teams), + ... int_range_to_dtype(-teams, teams)) + >>> map_games(perm, dest) + >>> print(dest) + [[ 2 -1] + [-2 1]] + >>> teams = 4 + >>> rounds = 2 + >>> perm = search_space_for_n_and_rounds(teams, rounds).blueprint + >>> dest = np.empty((rounds * (teams - 1), teams), + ... int_range_to_dtype(-teams, teams)) + >>> map_games(perm, dest) + >>> print(dest) + [[ 2 -1 4 -3] + [ 3 4 -1 -2] + [ 4 3 -2 -1] + [-2 1 -4 3] + [-3 -4 1 2] + [-4 -3 2 1]] + """ + y.fill(0) # first zero the output matrix + days, n = y.shape # the number of days and teams to be scheduled + div: Final[int] = n - 1 # the divisor for permutation values -> teams + + for game in x: + home_idx: int = (game // div) % n # home idx is in 0..n-1 + away_idx: int = game % div # away index in 0..n-2 + if away_idx >= home_idx: # "A vs. A" games impossible + away_idx += 1 # away index in 0..n-1, but != home_idx + + for day in range(days): # iterate over all possible rows for game + if (y[day, home_idx] != 0) or (y[day, away_idx] != 0): + continue # day already blocked + y[day, home_idx] = away_idx + 1 + y[day, away_idx] = -(home_idx + 1) + break
+ + + +
+[docs] +class GameEncoding(Encoding): + """An encoding that transforms strings of games to game plans.""" + + def __init__(self, instance: Instance) -> None: + """ + Create the game-based encoding. + + :param instance: the instance + """ + super().__init__() + #: the instance + self.instance: Final[Instance] = instance + self.decode = map_games # type: ignore + +
+[docs] + def search_space(self) -> Permutations: + """ + Create a proper search space for this game-based encoding. + + The search space is a set of :mod:`~moptipy.spaces.permutations` that + represents all the games that can take place in the tournament. + Depending on the number of + :attr:`~moptipyapps.ttp.instance.Instance.rounds` in the tournament, + some games may appear multiple times. Home and away games are + distributed in a fair and deterministic mannner between the teams. + + :return: the search space + + >>> inst = Instance.from_resource("circ4") + >>> inst.n_cities + 4 + >>> inst.rounds + 2 + >>> ";".join(map(str, GameEncoding(inst).search_space().blueprint)) + '0;1;2;3;4;5;6;7;8;9;10;11' + >>> inst = Instance(inst.name, inst, inst.teams, inst.rounds, + ... inst.home_streak_min, inst.home_streak_max, + ... inst.away_streak_min, inst.away_streak_max, + ... inst.separation_min, inst.separation_max) + >>> inst.rounds = 1 # modify number of rounds for copied instance + >>> ";".join(map(str, GameEncoding(inst).search_space().blueprint)) + '1;2;3;7;8;10' + >>> inst.rounds = 3 # modify number of rounds for copied instance + >>> ";".join(map(str, GameEncoding(inst).search_space().blueprint)) + '0;1;1;2;2;3;3;4;5;6;7;7;8;8;9;10;10;11' + >>> inst.rounds = 4 # modify number of rounds for copied instance + >>> ";".join(map(str, GameEncoding(inst).search_space().blueprint)) + '0;0;1;1;2;2;3;3;4;4;5;5;6;6;7;7;8;8;9;9;10;10;11;11' + >>> inst.rounds = 5 # modify number of rounds for copied instance + >>> ";".join(map(str, GameEncoding(inst).search_space().blueprint)) + '0;0;1;1;1;2;2;2;3;3;3;4;4;5;5;6;6;7;7;7;8;8;8;9;9;10;10;10;11;11' + """ + return search_space_for_n_and_rounds( + self.instance.n_cities, self.instance.rounds)
+
+ +
diff --git a/_modules/moptipyapps/ttp/game_plan.html b/_modules/moptipyapps/ttp/game_plan.html new file mode 100644 index 00000000..60ea1c07 --- /dev/null +++ b/_modules/moptipyapps/ttp/game_plan.html @@ -0,0 +1,125 @@ +moptipyapps.ttp.game_plan — moptipyapps 0.8.62 documentation

Source code for moptipyapps.ttp.game_plan

+"""
+A game plan assigns teams to games.
+
+A game plan is a two-dimensional matrix `G`. The rows are the time slots.
+There is one column for each time. If `G` has value `v` at row `i` and
+column `j`, then this means:
+
+- at the time slot `i` ...
+- the team with name `j+1` plays
+    + no team if `v == 0`,
+    + *at home* against the team `v` if `v > 0`, i.e., team `v` travels
+      to the home stadium of team `j+1`
+    + *away* against the team `-v` if `v < 0`, i.e., team `j+1` travels
+      to the home stadium of team `-v` and plays against them there
+
+Indices in matrices are zero-based, i.e., the lowest index for a row `i` is
+`0` and the lowest index for a column `j` is also `0`. However, team names
+are one-based, i.e., that with `1`. Therefore, we need to translate the
+zero-based column index `j` to a team name by adding `1` to it.
+
+This is just a numerical variant of the game plan representation given at
+<https://robinxval.ugent.be/RobinX/travelRepo.php>. Indeed, the `str(...)`
+representation of a game plan is exactly the table shown there.
+
+Of course, if `G[i, j] = v`, then `G[i, v - 1] = -(j + 1)` should hold if
+`v > 0`, for example. Vice versa, if `v < 0` and `G[i, j] = v`, then
+`G[i, (-v) - 1] = j + 1` should hold. Such constraints are checked by the
+:mod:`~moptipyapps.ttp.errors` objective function.
+
+The corresponding space implementation,
+:mod:`~moptipyapps.ttp.game_plan_space`, offers the functionality to convert
+strings to game plans as well as to instantiate them in a black-box algorithm.
+"""
+
+from io import StringIO
+from typing import Final
+
+import numpy as np
+from moptipy.api.component import Component
+from moptipy.utils.logger import CSV_SEPARATOR
+from pycommons.types import type_error
+
+from moptipyapps.ttp.instance import Instance
+
+
+
+[docs] +class GamePlan(Component, np.ndarray): + """A game plan, i.e., a solution to the Traveling Tournament Problem.""" + + #: the TTP instance + instance: Instance + + def __new__(cls, instance: Instance) -> "GamePlan": + """ + Create a solution record for the Traveling Tournament Problem. + + :param cls: the class + :param instance: the solution record + """ + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + + n: Final[int] = instance.n_cities # the number of teams + # each team plays every other team 'rounds' times + n_days: Final[int] = (n - 1) * instance.rounds + obj: Final[GamePlan] = super().__new__( + cls, (n_days, n), instance.game_plan_dtype) + #: the TTP instance + obj.instance = instance + return obj + + def __str__(self): + """ + Convert the game plan to a compact string. + + The first line of the output is a flattened version of this matrix + with the values being separated by `;`. Then we place an empty line. + + We then put a more easy-to-read representation and follow the pattern + given at https://robinxval.ugent.be/RobinX/travelRepo.php, which is + based upon the notation by Easton et al. Here, first, a row with the + team names separated by spaces is generated. Then, each row contains + the opponents of these teams, again separated by spaces. If an + opponent plays at their home, this is denoted by an `@`. + If a team has no scheduled opponent, then this is denoted as `-`. + + :return: the compact string + """ + csv: Final[str] = CSV_SEPARATOR + sep: str = "" + teams: Final[tuple[str, ...]] = self.instance.teams + len(teams) + + with StringIO() as sio: + for k in self.flatten(): + sio.write(sep) + sio.write(str(k)) + sep = csv + + sio.write("\n\n") + + sep = "" + for t in teams: + sio.write(sep) + sio.write(t) + sep = " " + + for row in self: + sio.write("\n") + sep = "" + for d in row: + sio.write(sep) + if d < 0: + sio.write(f"@{teams[-d - 1]}") + elif d > 0: + sio.write(teams[d - 1]) + else: + sio.write("-") + sep = " " + + return sio.getvalue()
+ +
diff --git a/_modules/moptipyapps/ttp/game_plan_space.html b/_modules/moptipyapps/ttp/game_plan_space.html new file mode 100644 index 00000000..fccb82ee --- /dev/null +++ b/_modules/moptipyapps/ttp/game_plan_space.html @@ -0,0 +1,231 @@ +moptipyapps.ttp.game_plan_space — moptipyapps 0.8.62 documentation

Source code for moptipyapps.ttp.game_plan_space

+"""
+Here we provide a :class:`~moptipy.api.space.Space` of bin game plans.
+
+The bin game plans object is defined in module
+:mod:`~moptipyapps.ttp.game_plan`. Basically, it is a
+two-dimensional numpy array holding, for each day (or time slot) for each team
+the opposing team.
+"""
+from typing import Final
+
+import numpy as np
+from moptipy.api.space import Space
+from moptipy.utils.logger import CSV_SEPARATOR, KeyValueLogSection
+from pycommons.types import type_error
+
+from moptipyapps.shared import SCOPE_INSTANCE
+from moptipyapps.ttp.game_plan import GamePlan
+from moptipyapps.ttp.instance import Instance
+
+
+
+[docs] +class GamePlanSpace(Space): + """An implementation of the `Space` API of for game plans.""" + + def __init__(self, instance: Instance) -> None: + """ + Create a 2D packing space. + + :param instance: the 2d bin packing instance + + >>> inst = Instance.from_resource("circ4") + >>> space = GamePlanSpace(inst) + >>> space.instance is inst + True + """ + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + #: The instance to which the packings apply. + self.instance: Final[Instance] = instance + self.copy = np.copyto # type: ignore + self.to_str = GamePlan.__str__ # type: ignore + +
+[docs] + def create(self) -> GamePlan: + """ + Create a game plan without assigning items to locations. + + :return: the (empty, uninitialized) packing object + + >>> inst = Instance.from_resource("circ8") + >>> space = GamePlanSpace(inst) + >>> x = space.create() + >>> print(inst.rounds) + 2 + >>> print(inst.n_cities) + 8 + >>> x.shape + (14, 8) + >>> x.instance is inst + True + >>> type(x) + <class 'moptipyapps.ttp.game_plan.GamePlan'> + """ + return GamePlan(self.instance)
+ + +
+[docs] + def is_equal(self, x1: GamePlan, x2: GamePlan) -> bool: + """ + Check if two bin game plans have the same contents. + + :param x1: the first game plan + :param x2: the second game plan + :return: `True` if both game plans are for the same instance and have + the same structure + + >>> inst = Instance.from_resource("circ4") + >>> space = GamePlanSpace(inst) + >>> y1 = space.create() + >>> y1.fill(0) + >>> y2 = space.create() + >>> y2.fill(0) + >>> space.is_equal(y1, y2) + True + >>> y1 is y2 + False + >>> y1[0, 0] = 1 + >>> space.is_equal(y1, y2) + False + """ + return (x1.instance is x2.instance) and np.array_equal(x1, x2)
+ + +
+[docs] + def from_str(self, text: str) -> GamePlan: + """ + Convert a string to a packing. + + :param text: the string + :return: the packing + + >>> inst = Instance.from_resource("circ6") + >>> space = GamePlanSpace(inst) + >>> y1 = space.create() + >>> y1.fill(0) + >>> y2 = space.from_str(space.to_str(y1)) + >>> space.is_equal(y1, y2) + True + >>> y1 is y2 + False + """ + if not isinstance(text, str): + raise type_error(text, "packing text", str) + + # we only want the very first line + text = text.lstrip() + lb: int = text.find("\n") + if lb > 0: + text = text[:lb].rstrip() + + x: Final[GamePlan] = self.create() + np.copyto(x, np.fromstring(text, dtype=x.dtype, sep=CSV_SEPARATOR) + .reshape(x.shape)) + self.validate(x) + return x
+ + +
+[docs] + def validate(self, x: GamePlan) -> None: + """ + Check if a game plan is an instance of the right object. + + This method performs a superficial feasibility check, as in the TTP, + we try to find feasible game plans and may have infeasible ones. All + we check here is that the object is of the right type and dimensions + and that it does not contain some out-of-bounds value. + + :param x: the game plan + :raises TypeError: if any component of the game plan is of the wrong + type + :raises ValueError: if the game plan is not feasible + """ + if not isinstance(x, GamePlan): + raise type_error(x, "x", GamePlan) + inst: Final[Instance] = self.instance + if inst is not x.instance: + raise ValueError( + f"x.instance must be {inst} but is {x.instance}.") + if inst.game_plan_dtype is not x.dtype: + raise ValueError(f"inst.game_plan_dtype = {inst.game_plan_dtype}" + f" but x.dtype={x.dtype}") + + n: Final[int] = inst.n_cities # the number of teams + # each team plays every other team 'rounds' times + n_days: Final[int] = (n - 1) * inst.rounds + + needed_shape: Final[tuple[int, int]] = (n_days, n) + if x.shape != needed_shape: + raise ValueError(f"x.shape={x.shape}, but must be {needed_shape}.") + min_id: Final[int] = -n + + for i in range(n_days): + for j in range(n): + v = x[i, j] + if not (min_id <= v <= n): + raise ValueError(f"value {v} at x[{i}, {j}] should be in " + f"{min_id}...{n}, but is not.")
+ + +
+[docs] + def n_points(self) -> int: + """ + Get the number of game plans. + + The values in a game plan go from `-n..n`, including zero, and we have + `days*n` values. This gives `(2n + 1) ** (days * n)`, where `days` + equals `(n - 1) * rounds` and `rounds` is the number of rounds. In + total, this gives `(2n + 1) ** ((n - 1) * rounds * n)`. + + :return: the number of possible game plans + + >>> space = GamePlanSpace(Instance.from_resource("circ6")) + >>> print((2 * 6 + 1) ** ((6 - 1) * 2 * 6)) + 6864377172744689378196133203444067624537070830997366604446306636401 + >>> space.n_points() + 6864377172744689378196133203444067624537070830997366604446306636401 + >>> space = GamePlanSpace(Instance.from_resource("circ4")) + >>> space.n_points() + 79766443076872509863361 + >>> print((2 * 4 + 1) ** ((4 - 1) * 2 * 4)) + 79766443076872509863361 + """ + inst: Final[Instance] = self.instance + n: Final[int] = inst.n_cities + n_days: Final[int] = (n - 1) * inst.rounds + total_values: Final[int] = 2 * n + 1 + return total_values ** (n_days * n)
+ + + def __str__(self) -> str: + """ + Get the name of the game plan space. + + :return: the name, simply `gp_` + the instance name + + >>> print(GamePlanSpace(Instance.from_resource("bra24"))) + gp_bra24 + """ + return f"gp_{self.instance}" + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log the parameters of the space to the given logger. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + with logger.scope(SCOPE_INSTANCE) as kv: + self.instance.log_parameters_to(kv)
+
+ +
diff --git a/_modules/moptipyapps/ttp/instance.html b/_modules/moptipyapps/ttp/instance.html new file mode 100644 index 00000000..4f9ee8c0 --- /dev/null +++ b/_modules/moptipyapps/ttp/instance.html @@ -0,0 +1,568 @@ +moptipyapps.ttp.instance — moptipyapps 0.8.62 documentation

Source code for moptipyapps.ttp.instance

+"""
+An instance of the Traveling Tournament Problem (TTP).
+
+The Traveling Tournament Problem (TTP) describes the logistics of a sports
+league. In this league, `n` teams compete. In each time slot, each team plays
+against one other team. In each game, one team plays at home and one team
+plays away with the other team. In each round, every team plays once against
+every other team. The league may have multiple :attr:`~moptipyapps.ttp.\
+instance.Instance.rounds`. If there are two rounds, then each team plays
+against each other team once at home and once abroad. If a team plays at home
+(or abroad) several times in a row, this is called a "streak". There are
+minimum and maximum streak length constraints defined, for both at home and
+abroad. Additionally, if team A plays a team B in one time slot, then the
+exact inverse game cannot take place in the next time slot. A minimum
+number of games must take place in between for separation. There can also be
+a maximum separation length.
+
+David Van Bulck of the Sports Scheduling Research group, part of the
+Faculty of Economics and Business Administration at Ghent University, Belgium,
+maintains "RobinX: An XML-driven Classification for Round-Robin Sports
+Timetabling", a set of benchmark data instances and results of the TTP.
+We provide some of these instances as resources here. You can also download
+them directly at <https://robinxval.ugent.be/RobinX/travelRepo.php>. Also,
+see <https://robinxval.ugent.be/> for more information.
+
+1. David Van Bulck. Minimum Travel Objective Repository. *RobinX: An
+   XML-driven Classification for Round-Robin Sports Timetabling.* Faculty of
+   Economics and Business Administration at Ghent University, Belgium.
+   https://robinxval.ugent.be/
+2. Kelly Easton, George L. Nemhauser, and Michael K. Trick. The Traveling
+   Tournament Problem Description and Benchmarks. In *Principles and Practice
+   of Constraint Programming (CP'01),*  November 26 - December 1, 2001, Paphos,
+   Cyprus, pages 580-584, Berlin/Heidelberg, Germany: Springer.
+   ISBN: 978-3-540-42863-3. https://doi.org/10.1007/3-540-45578-7_43
+   https://www.researchgate.net/publication/220270875
+"""
+
+from typing import Callable, Final, Iterable, TextIO, cast
+
+import numpy as np
+from defusedxml import ElementTree  # type: ignore
+from moptipy.utils.logger import KeyValueLogSection
+from moptipy.utils.nputils import DEFAULT_INT, int_range_to_dtype
+from moptipy.utils.strings import sanitize_name
+from pycommons.io.path import Path, file_path
+from pycommons.types import check_int_range, check_to_int_range, type_error
+
+from moptipyapps.tsp.instance import Instance as TSPInstance
+from moptipyapps.ttp.robinx import open_resource_stream
+
+
+def _from_stream(stream: TextIO) -> "Instance":
+    """
+    Read a TTP instance from an `robinxval.ugent.be`-formatted XML file.
+
+    This procedure ignores most of the data in the file and only focuses on
+    the instance name, the team names, and the distance matrix as well as the
+    constraints for home streak length, away streak length, and same-game
+    separations. Everything else is ignored.
+
+    :param stream: the text stream
+    :return: the instance
+    """
+    used_names: set[str] = set()
+    team_names: dict[int, str] = {}
+    distances: dict[tuple[int, int], int] = {}
+    name: str | None = None
+    rounds: int | None = None
+    home_streak_min: int | None = None
+    home_streak_max: int | None = None
+    away_streak_min: int | None = None
+    away_streak_max: int | None = None
+    separation_min: int | None = None
+    separation_max: int | None = None
+
+    for _event, element in ElementTree.iterparse(stream, forbid_dtd=True,
+                                                 forbid_entities=True,
+                                                 forbid_external=True):
+        if element.tag is None:
+            continue
+        tag: str = element.tag.strip().lower()
+        if tag == "instancename":
+            if name is None:
+                name = sanitize_name(element.text).lower()
+                if name in used_names:
+                    raise ValueError(
+                        f"name {element.text!r} invalid, as it "
+                        f"maps to {name!r}, which is already used.")
+                used_names.add(name)
+            else:
+                raise ValueError(f"already got name={name!r}, but tag "
+                                 f"'InstanceName' appears again?")
+        elif tag == "distance":
+            t1: int = check_to_int_range(element.attrib["team1"],
+                                         "team1", 0, 1_000_000)
+            t2: int = check_to_int_range(element.attrib["team2"],
+                                         "team2", 0, 1_000_000)
+            dst: int = check_to_int_range(
+                element.attrib["dist"], "dist", 0, 1_000_000_000_000)
+            if t1 == t2:
+                if dst == 0:
+                    continue
+                raise ValueError(f"distance for team1={t1}, team2={t2} is"
+                                 f" {dst} but must be 0.")
+            tpl: tuple[int, int] = (t1, t2)
+            if tpl in distances:
+                raise ValueError(
+                    f"got distance={dst} for {tpl!r}, but "
+                    f"{distances[tpl]} was already specified before")
+            distances[tpl] = dst
+        elif element.tag == "team":
+            team: int = check_to_int_range(element.attrib["id"], "id",
+                                           0, 1_000_000)
+            nn: str = element.attrib["name"]
+            tname: str = nn.strip()
+            if tname in used_names:
+                raise ValueError(f"name {nn!r} is invalid, as it maps to "
+                                 f"{tname!r}, which is already used.")
+            used_names.add(tname)
+            team_names[team] = tname
+        elif element.tag == "numberroundrobin":
+            if rounds is not None:
+                raise ValueError(f"rounds already set to {rounds}")
+            rounds = check_to_int_range(element.text, "rounds", 1, 1000)
+        elif tag == "ca3":
+            if "mode1" not in element.attrib:
+                continue
+            if "mode2" not in element.attrib:
+                continue
+            if element.attrib["mode2"].lower() != "games":
+                continue
+            mode = element.attrib["mode1"].lower()
+            mi = check_to_int_range(
+                element.attrib["min"], "min", 0, 1_000_000) \
+                if "min" in element.attrib else None
+            ma = check_to_int_range(
+                element.attrib["max"], "max", 1, 1_000_000) \
+                if "max" in element.attrib else None
+            if mode == "h":
+                if mi is not None:
+                    if home_streak_min is not None:
+                        raise ValueError("minimum home streak already defined")
+                    home_streak_min = max(mi, 1)
+                if ma is not None:
+                    if home_streak_max is not None:
+                        raise ValueError("maximum home streak already defined")
+                    home_streak_max = ma
+            elif mode == "a":
+                if mi is not None:
+                    if away_streak_min is not None:
+                        raise ValueError("minimum away streak already defined")
+                    away_streak_min = max(mi, 1)
+                if ma is not None:
+                    if away_streak_max is not None:
+                        raise ValueError("maximum away streak already defined")
+                    away_streak_max = ma
+        elif tag == "se1":
+            mi = check_to_int_range(
+                element.attrib["min"], "min", 0, 1_000_000) \
+                if "min" in element.attrib else None
+            ma = check_to_int_range(
+                element.attrib["max"], "max", 0, 1_000_000) \
+                if "max" in element.attrib else None
+            if mi is not None:
+                if separation_min is not None:
+                    raise ValueError("minimum separation already defined")
+                separation_min = mi
+            if ma is not None:
+                if separation_max is not None:
+                    raise ValueError("maximum separation already defined")
+                separation_max = ma
+
+    if name is None:
+        raise ValueError("did not find instance name")
+    n_teams: Final[int] = len(team_names)
+    if n_teams <= 0:
+        raise ValueError("did not find any team name")
+    if len(used_names) != (n_teams + 1):
+        raise ValueError(f"set of used names {used_names!r} has wrong "
+                         f"length, should be {n_teams + 1}.")
+    dm: np.ndarray = np.zeros((n_teams, n_teams), DEFAULT_INT)
+    for tup, dst in distances.items():
+        dm[tup[0], tup[1]] = dst
+
+    if rounds is None:
+        rounds = 2
+    ll: Final[int] = rounds * n_teams - 1
+    if home_streak_min is None:
+        home_streak_min = 1
+    if home_streak_max is None:
+        home_streak_max = min(max(home_streak_min, 3), ll)
+    if away_streak_min is None:
+        away_streak_min = 1
+    if away_streak_max is None:
+        away_streak_max = min(max(away_streak_min, 3), ll)
+    if separation_min is None:
+        separation_min = 1
+    if separation_max is None:
+        separation_max = min(max(separation_min, 1), ll)
+
+    return Instance(name, dm, [team_names[i] for i in range(n_teams)],
+                    rounds, home_streak_min, home_streak_max, away_streak_min,
+                    away_streak_max, separation_min, separation_max)
+
+
+#: The instances made available within this package are taken from
+#: <https://robinxval.ugent.be/RobinX/travelRepo.php>, where the following
+#: descriptions are given:
+#: - *Constant Distance (`con*`):* The constant distance instances are the
+#:   most simple instances in which the distance between the home venues of
+#:   any two teams is one. In this case, Urrutia and Ribeiro showed that
+#:   distance minimization is equivalent with break maximization.
+#: - *Circular Distance (`circ*`):* Somewhat similar are the circular
+#:   distance instances in which the teams' venues are placed on a
+#:   circle. Any two consecutive teams are connected by an edge and the
+#:   distance between two teams is equal to the minimal number of edges that
+#:   must be traversed to get to the other team. Although traveling
+#:   salesperson problems with a circular distance matrix have a trivial
+#:   solution, it remains challenging to solve circular traveling tournament
+#:   instances.
+#: - *Galaxy (`gal*`):* This artificial instance class consists of the Galaxy
+#:   instances that use a 3D-space that embeds the Earth and 39 other
+#:   exoplanets.
+#: - *National league (`nl*`):* The `nl`-instances are based on air distance
+#:   between the city centers from teams in the National League of the Major
+#:   League Baseball.
+#: - *National football league (`nfl*`):* The NFL-instances are based on air
+#:   distance between the city centers from teams in the National Football
+#:   League.
+#: - *Super 14 (`sup*`):* The super 14 instances are based on air distance
+#:   between the city centers from teams in the Super 14 rugby cup.
+#: - *Brazilian (`bra24`)):* The Brazilian instance is based on the air
+#:   distance between the home cities of 24 teams in the main division of the
+#:   2003 edition of the Brazilian soccer championship.
+#: - *Linear (`line*`):* In the linear instances, `n` teams are located on a
+#:   straight line with a distance of one unit separating each pair of
+#:   adjacent teams.
+#: - *Increasing distance (`incr*`):* In the increasing distance instances,
+#:   `n` teams are located on a straight line with an increasing distance
+#:   separating each pair of adjacent teams such that the distance between
+#:   team `k` and `k+1` equals `k`.
+_INSTANCES: Final[tuple[str, ...]] = (
+    "bra24", "circ4", "circ6", "circ8", "circ10", "circ12", "circ14",
+    "circ16", "circ18", "circ20", "circ22", "circ24", "circ26", "circ28",
+    "circ30", "circ32", "circ34", "circ36", "circ38", "circ40", "con4",
+    "con6", "con8", "con10", "con12", "con14", "con16", "con18", "con20",
+    "con22", "con24", "con26", "con28", "con30", "con32", "con34", "con36",
+    "con38", "con40", "gal4", "gal6", "gal8", "gal10", "gal12", "gal14",
+    "gal16", "gal18", "gal20", "gal22", "gal24", "gal26", "gal28", "gal30",
+    "gal32", "gal34", "gal36", "gal38", "gal40", "incr4", "incr6", "incr8",
+    "incr10", "incr12", "incr14", "incr16", "incr18", "incr20", "incr22",
+    "incr24", "incr26", "incr28", "incr30", "incr32", "incr34", "incr36",
+    "incr38", "incr40", "line4", "line6", "line8", "line10", "line12",
+    "line14", "line16", "line18", "line20", "line22", "line24", "line26",
+    "line28", "line30", "line32", "line34", "line36", "line38", "line40",
+    "nfl16", "nfl18", "nfl20", "nfl22", "nfl24", "nfl26", "nfl28", "nfl30",
+    "nfl32", "nl4", "nl6", "nl8", "nl10", "nl12", "nl14", "nl16", "sup4",
+    "sup6", "sup8", "sup10", "sup12", "sup14")
+
+
+#: The lower and upper bound for the *optimal* total tournament length, taken
+#: from https://robinxval.ugent.be/RobinX/travelRepo.php on 2024-05-10.
+_OPT_DISTANCE_BOUNDS: Final[dict[str, tuple[int, int]]] = {
+    "bra24": (451406, 538866), "circ4": (20, 20), "circ6": (64, 64),
+    "circ8": (132, 132), "circ10": (242, 242), "circ12": (388, 400),
+    "circ14": (588, 616), "circ16": (846, 898), "circ18": (1188, 1268),
+    "circ20": (1600, 1724), "circ22": (2068, 2366), "circ24": (2688, 3146),
+    "circ26": (3380, 3992), "circ28": (4144, 4642), "circ30": (5100, 5842),
+    "circ32": (6144, 7074), "circ34": (7276, 8042), "circ36": (8640, 9726),
+    "circ38": (10108, 11424), "circ40": (11680, 12752), "con4": (17, 17),
+    "con6": (43, 43), "con8": (80, 80), "con10": (124, 124),
+    "con12": (181, 181), "con14": (252, 252), "con16": (327, 327),
+    "con18": (414, 416), "con20": (520, 520), "con22": (626, 626),
+    "con24": (744, 747), "con26": (884, 884), "con28": (1021, 1021),
+    "con30": (1170, 1177), "con32": (1344, 1359), "con34": (1512, 1512),
+    "con36": (1692, 1703), "con38": (1900, 1918), "con40": (2099, 2099),
+    "gal4": (416, 416), "gal6": (1365, 1365), "gal8": (2373, 2373),
+    "gal10": (4535, 4535), "gal12": (7034, 7135), "gal14": (10255, 10840),
+    "gal16": (13619, 14583), "gal18": (19050, 20205), "gal20": (23738, 25401),
+    "gal22": (31461, 33901), "gal24": (41287, 44260), "gal26": (53802, 58968),
+    "gal28": (69992, 75276), "gal30": (88831, 95158),
+    "gal32": (108374, 119665), "gal34": (133976, 143298),
+    "gal36": (158549, 169387), "gal38": (189126, 204980),
+    "gal40": (226820, 241908), "incr4": (48, 48), "incr6": (228, 228),
+    "incr8": (624, 638), "incr10": (1440, 1612), "incr12": (2880, 3398),
+    "incr14": (5180, 6488), "incr16": (8640, 10332), "incr18": (13548, 17278),
+    "incr20": (20368, 25672), "incr22": (29484, 40944),
+    "incr24": (41360, 56602), "incr26": (56500, 81866),
+    "incr28": (75456, 106870), "incr30": (98820, 136810),
+    "incr32": (127224, 177990), "incr34": (161348, 222082),
+    "incr36": (201912, 278060), "incr38": (249686, 336008),
+    "incr40": (305470, 406960), "line4": (24, 24), "line6": (76, 76),
+    "line8": (156, 162), "line10": (288, 370), "line12": (480, 584),
+    "line14": (740, 918), "line16": (1080, 1320), "line18": (1512, 1926),
+    "line20": (2044, 2548), "line22": (2688, 3684), "line24": (3456, 4732),
+    "line26": (4356, 6382), "line28": (5400, 7778), "line30": (6600, 9312),
+    "line32": (7964, 11234), "line34": (9504, 13190),
+    "line36": (11232, 15536), "line38": (13156, 17862),
+    "line40": (15294, 20546), "nfl16": (223800, 231483),
+    "nfl18": (272834, 282258), "nfl20": (316721, 332041),
+    "nfl22": (378813, 400636), "nfl24": (431226, 463657),
+    "nfl26": (495982, 536792), "nfl28": (560697, 598123),
+    "nfl30": (688875, 739697), "nfl32": (836031, 914620),
+    "nl4": (8276, 8276), "nl6": (23916, 23916), "nl8": (39721, 39721),
+    "nl10": (59436, 59436), "nl12": (108629, 110729),
+    "nl14": (183354, 188728), "nl16": (249477, 261687),
+    "sup4": (63405, 63405), "sup6": (130365, 130365),
+    "sup8": (182409, 182409), "sup10": (316329, 316329),
+    "sup12": (453860, 458810), "sup14": (557354, 567891),
+}
+
+
+
+[docs] +class Instance(TSPInstance): + """An instance of Traveling Tournament Problem (TTP).""" + + #: the names of the teams + teams: tuple[str, ...] + #: the number of rounds + rounds: int + #: the minimum number of games that can be played at home in a row + home_streak_min: int + #: the maximum number of games that can be played at home in a row + home_streak_max: int + #: the minimum number of games that can be played away in a row + away_streak_min: int + #: the maximum number of games that can be played away in a row + away_streak_max: int + #: the minimum number of games between a repetition of a game setup + separation_min: int + #: the maximum number of games between a repetition of a game setup + separation_max: int + #: the data type to be used for plans + game_plan_dtype: np.dtype + + def __new__(cls, name: str, matrix: np.ndarray, teams: Iterable[str], + rounds: int, home_streak_min: int, home_streak_max: int, + away_streak_min: int, away_streak_max: int, + separation_min: int, separation_max: int, + tour_length_lower_bound: int = 0) -> "Instance": + """ + Create an instance of the Traveling Salesperson Problem. + + :param cls: the class + :param name: the name of the instance + :param matrix: the matrix with the data (will be copied) + :param teams: the iterable with the team names + :param rounds: the number of rounds + :param tour_length_lower_bound: the lower bound of the tour length + :param home_streak_min: the minimum number of games that can be played + at home in a row + :param home_streak_max: the maximum number of games that can be played + at home in a row + :param away_streak_min: the minimum number of games that can be played + away in a row + :param away_streak_max: the maximum number of games that can be played + away in a row + :param separation_min: the minimum number of games between a repetition + of a game setup + :param separation_max: the maximum number of games between a repetition + of a game setup + """ + names: Final[tuple[str, ...]] = tuple(map(str.strip, teams)) + n: Final[int] = len(names) + if (n % 2) != 0: + raise ValueError(f"the number of teams must be even, but is {n}.") + if n != len(set(names)): + raise ValueError(f"some team name appears twice in {teams!r} " + f"after fixing it to {names!r}.") + for nn in names: + for char in nn: + if char.isspace(): + raise ValueError( + f"team name must not contain space, but {nn} does.") + + obj: Final[Instance] = cast(Instance, super().__new__( + cls, name, tour_length_lower_bound, matrix, rounds * n)) + + if (obj.shape[0] != n) or (obj.shape[1] != n) or (obj.n_cities != n): + raise ValueError(f"inconsistent n_teams={n}, n_cities=" + f"{obj.n_cities} and shape={obj.shape}") + #: the names of the teams that compete + obj.teams = names + #: the number of rounds + obj.rounds = check_int_range(rounds, "rounds", 1, 100) + ll: Final[int] = rounds * n - 1 # an upper bound for streaks + #: the minimum number of games that can be played at home in a row + obj.home_streak_min = check_int_range( + home_streak_min, "home_streak_min", 1, ll) + #: the maximum number of games that can be played at home in a row + obj.home_streak_max = check_int_range( + home_streak_max, "home_streak_max", home_streak_min, ll) + #: the minimum number of games that can be played away in a row + obj.away_streak_min = check_int_range( + away_streak_min, "away_streak_min", 1, ll) + #: the maximum number of games that can be played away in a row + obj.away_streak_max = check_int_range( + away_streak_max, "away_streak_max", away_streak_min, ll) + #: the minimum number of games between a repetition of a game setup + obj.separation_min = check_int_range( + separation_min, "separation_min", 0, ll) + #: the maximum number of games between a repetition of a game setup + obj.separation_max = check_int_range( + separation_max, "separation_max", separation_min, ll) + #: the data type to be used for the game plans + obj.game_plan_dtype = int_range_to_dtype(-n, n) + return obj + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log the parameters of the instance to the given logger. + + :param logger: the logger for the parameters + + >>> from moptipy.utils.logger import InMemoryLogger + >>> with InMemoryLogger() as l: + ... with l.key_values("I") as kv: + ... Instance.from_resource("gal4").log_parameters_to(kv) + ... print(repr('@'.join(l.get_log()))) + 'BEGIN_I@name: gal4@class: moptipyapps.ttp.instance.Instance@\ +nCities: 4@tourLengthLowerBound: 67@tourLengthUpperBound: 160@symmetric: T@\ +dtype: h@rounds: 2@homeStreakMin: 1@homeStreakMax: 3@\ +awayStreakMin: 1@awayStreakMax: 3@separationMin: 1@separationMax: 6@\ +gamePlanDtype: b@END_I' + """ + super().log_parameters_to(logger) + logger.key_value("rounds", self.rounds) + logger.key_value("homeStreakMin", self.home_streak_min) + logger.key_value("homeStreakMax", self.home_streak_max) + logger.key_value("awayStreakMin", self.away_streak_min) + logger.key_value("awayStreakMax", self.away_streak_max) + logger.key_value("separationMin", self.separation_min) + logger.key_value("separationMax", self.separation_max) + logger.key_value("gamePlanDtype", self.game_plan_dtype.char)
+ + +
+[docs] + def get_optimal_plan_length_bounds(self) -> tuple[int, int]: + """ + Get lower and upper bounds in which the *optimal* plan length resides. + + These are the bounds for the optimal tour length of *feasible* + solutions. If we know the feasible solution with the smallest possible + tour length, then the :class:`~moptipyapps.ttp.game_plan` objective + function would return a value within these limits for this solution. + The limits for the RobinX instance have been taken from + https://robinxval.ugent.be/RobinX/travelRepo.php on 2024-05-10. + + :return: a tuple of the lower and upper limit for the optimal + plan length + """ + if self.name in _OPT_DISTANCE_BOUNDS: + return _OPT_DISTANCE_BOUNDS[self.name] + # unknown instance, compute bounds including penalty + n: Final[int] = self.n_cities + rounds: Final[int] = self.rounds + days: Final[int] = (n - 1) * rounds + return 0, ((2 * int(self.max())) + 1) * n * days
+ + +
+[docs] + @staticmethod + def from_file(path: str, lower_bound_getter: Callable[[ + str], int] | None = None) -> "Instance": + """ + Read a TTP instance from a `robinX` formatted XML file. + + :param path: the path to the file + :param lower_bound_getter: ignored + :return: the instance + + >>> from os.path import dirname + >>> inst = Instance.from_file(dirname(__file__) + "/robinx/con20.xml") + >>> inst.name + 'con20' + """ + file: Final[Path] = file_path(path) + with file.open_for_read() as stream: + try: + return _from_stream(cast(TextIO, stream)) + except (TypeError, ValueError) as err: + raise ValueError(f"error when parsing file {file!r}") from err
+ + +
+[docs] + @staticmethod + def list_resources(symmetric: bool = True, + asymmetric: bool = True) -> tuple[str, ...]: + """ + Get a tuple of all the TTP instances available as resource. + + All instances of the `robinX` set provided here are symmetric. + + :param symmetric: include the instances with symmetric distance + matrices + :param asymmetric: include the asymmetric instances with asymmetric + distance matrices + :return: the tuple with the instance names + + >>> len(Instance.list_resources()) + 118 + >>> len(Instance.list_resources(False, True)) + 0 + >>> len(Instance.list_resources(True, False)) + 118 + """ + return _INSTANCES if symmetric else ()
+ + +
+[docs] + @staticmethod + def from_resource(name: str) -> "Instance": + """ + Load a TTP instance from a resource. + + :param name: the name string + :return: the instance + + >>> insta = Instance.from_resource("bra24") + >>> insta.n_cities + 24 + >>> insta.name + 'bra24' + >>> insta.teams[0] + 'Atl.Mineiro' + >>> insta.teams[1] + 'Atl.Paranaense' + >>> insta.rounds + 2 + >>> insta.home_streak_min + 1 + >>> insta.home_streak_max + 3 + >>> insta.away_streak_min + 1 + >>> insta.away_streak_max + 3 + >>> insta.separation_min + 1 + >>> insta.separation_max + 46 + """ + if not isinstance(name, str): + raise type_error(name, "name", str) + container: Final = Instance.from_resource + inst_attr: Final[str] = f"__inst_{name}" + if hasattr(container, inst_attr): # instance loaded? + return cast(Instance, getattr(container, inst_attr)) + + with open_resource_stream(f"{name}.xml") as stream: + inst: Final[Instance] = _from_stream(stream) + + if inst.name != name: + raise ValueError(f"got {inst.name!r} for instance {name!r}?") + if inst.n_cities <= 1000: + setattr(container, inst_attr, inst) + return inst
+
+ +
diff --git a/_modules/moptipyapps/ttp/plan_length.html b/_modules/moptipyapps/ttp/plan_length.html new file mode 100644 index 00000000..3b717aaf --- /dev/null +++ b/_modules/moptipyapps/ttp/plan_length.html @@ -0,0 +1,224 @@ +moptipyapps.ttp.plan_length — moptipyapps 0.8.62 documentation

Source code for moptipyapps.ttp.plan_length

+"""
+An objective computing the total travel length of all teams in a game plan.
+
+This objective function takes a game plan as argument and computes the total
+travel length for all teams.
+A :mod:`~moptipyapps.ttp.game_plan` is basically a matrix that, for each day
+(first dimension) stores against which each team (second dimension) plays.
+If a team plays at home (has a home game), its opponent is stored as a
+positive number.
+If the team has an away game, i.e., needs to visit the opponent, then this
+opponent is stored as a negative number.
+Team IDs go from `1` to `n`, i.e., a value in `1..n` indicates a home game
+and a value in `-n..-1` indicates an away game.
+The value `0` denotes a `bye`, i.e., that no game is scheduled for a team
+at the given day.
+
+The total game plan length is computed as follows:
+
+1. the total length = 0
+2. for each team,
+    1. start at the current location = home
+    2. for each day,
+        1. if the opponent number is negative, the next location is the
+           opponent's hometown;
+        2. else if the opponent number is positive, the next location is the
+           own hometown;
+        3. else (if the opponent number is 0): add the `bye penalty` to the
+           total length and jump to the next iteration
+        4. add the distance from the current to the next location to the total
+           length
+        5. set the current location = the next location
+    3. if the current location != own hometown, add the travel distance back
+       from the current location to the hometown to the total travel length.
+
+As penalty for the `bye` situation where no game is scheduled, we use twice
+the maximum distance between any two teams plus 1.
+The logic is that if a `bye` (i.e., a `0`) inserted into a game plan, it
+replaces one game. Since it replaces one game, it affects up to two travels,
+namely from the previous location to the game location and from the game
+location to the next location.
+So the optimization process could sneakily try to cut longer legs of the
+tournament by inserting a `bye`.
+The longest imaginable travel would be between the two cities that are
+farthest away from each other and back.
+By making the penalty for a `bye` exactly one distance unit longer than
+this longest imaginable distance, we ensure that the travel length can
+never be reduced by inserting a `bye`.
+Thus, having a `bye` is always more costly than any other travel it could
+replace.
+"""
+
+
+from typing import Final
+
+import numba  # type: ignore
+import numpy as np
+from moptipy.api.objective import Objective
+from moptipy.utils.logger import KeyValueLogSection
+from pycommons.types import type_error
+
+from moptipyapps.ttp.game_plan import GamePlan
+from moptipyapps.ttp.instance import Instance
+
+
+
+[docs] +@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) +def game_plan_length( + y: np.ndarray, distances: np.ndarray, bye_penalty: int) -> int: + """ + Compute the total travel length of a game plan. + + :param y: the game plan + :param distances: the distance matrix + :param bye_penalty: the penalty for `bye = 0` entries, i.e., days where + no game is scheduled + :returns: the total plan length + + >>> yy = np.array([[ 2, -1, 4, -3], + ... [-2, 1, -4, 3], + ... [ 3, 4, -1, -2], + ... [-3, -4, 1, 2], + ... [ 4, 3, -2, -1], + ... [-4, -3, 2, 1]], int) + >>> dd = np.array([[ 0, 1, 2, 3], + ... [ 7, 0, 4, 5], + ... [ 8, 10, 0, 6], + ... [ 9, 11, 12, 0]], int) + >>> 0 + 1 + 7 + 2 + 8 + 3 + 9 # team 1 + 30 + >>> 7 + 1 + 0 + 5 + 11 + 4 + 10 # team 2 + 38 + >>> 0 + 6 + 9 + 2 + 10 + 4 # team 3 + 31 + >>> 12 + 6 + 11 + 5 + 9 + 3 # team 4 + 46 + >>> 30 + 38 + 31 + 46 # total sum + 145 + >>> game_plan_length(yy, dd, 0) + 145 + + >>> yy[1, 0] = 0 # add a bye + >>> 0 + 25 + 0 + 2 + 8 + 3 + 9 # team 1 + 47 + >>> game_plan_length(yy, dd, 2 * 12 + 1) + 162 + """ + days, teams = y.shape # get the number of days and teams + length: int = 0 + + for team in range(teams): # for each team + current_location: int = team # start at home + for day in range(days): # for each day + next_location: int = y[day, team] + if next_location < 0: # away game at other team + next_location = (-next_location) - 1 + elif next_location > 0: # home game at home + next_location = team + else: # bye = no game = penalty + length += bye_penalty + continue # team stays in current location + if current_location == next_location: + continue # no move + length += distances[current_location, next_location] + current_location = next_location + + if current_location != team: # go back home + length += distances[current_location, team] + + return int(length)
+ + + +
+[docs] +class GamePlanLength(Objective): + """ + Compute the total travel length of a game plan. + + This objective function sums up all the travel lengths over all teams. + Days without game (`bye`) are penalized. + """ + + def __init__(self, instance: Instance) -> None: + """ + Initialize the game plan length objective function. + + :param instance: the TTP instance + """ + if not isinstance(instance, Instance): + raise type_error(instance, "instance", Instance) + super().__init__() + + #: the TTP instance + self.instance: Final[Instance] = instance + #: the bye penalty + self.bye_penalty: Final[int] = (2 * int(instance.max())) + 1 + +
+[docs] + def evaluate(self, x: GamePlan) -> int: + """ + Count the errors in a game plan as objective value. + + :param x: the game plan + :return: the number of errors in the plan + """ + return game_plan_length(x, x.instance, self.bye_penalty)
+ + +
+[docs] + def lower_bound(self) -> int: + """ + Obtain the lower bound for the travel length. + + :return: `0` + """ + return 0
+ + +
+[docs] + def upper_bound(self) -> int: + """ + Compute upper bound for the travel length: All `n*days*bye_penalty`. + + :returns: `n * days * self.bye_penalty` + """ + n: Final[int] = self.instance.n_cities + rounds: Final[int] = self.instance.rounds + days: Final[int] = (n - 1) * rounds + return n * days * self.bye_penalty
+ + +
+[docs] + def is_always_integer(self) -> bool: + """ + State that this objective function is always integer-valued. + + :return: `True` + """ + return True
+ + + def __str__(self) -> str: + """Get the name of this objective function.""" + return "planLength" + +
+[docs] + def log_parameters_to(self, logger: KeyValueLogSection) -> None: + """ + Log the parameters of the instance to the given logger. + + :param logger: the logger for the parameters + """ + super().log_parameters_to(logger) + logger.key_value("byePenalty", self.bye_penalty)
+
+ +
diff --git a/_modules/moptipyapps/ttp/robinx.html b/_modules/moptipyapps/ttp/robinx.html new file mode 100644 index 00000000..347e8b3d --- /dev/null +++ b/_modules/moptipyapps/ttp/robinx.html @@ -0,0 +1,37 @@ +moptipyapps.ttp.robinx — moptipyapps 0.8.62 documentation

Source code for moptipyapps.ttp.robinx

+"""
+The `RobinX` example data for the Traveling Tournament Problem (TTP).
+
+David Van Bulck of the Sports Scheduling Research group, part of the
+Faculty of Economics and Business Administration at Ghent University, Belgium,
+maintains "RobinX: An XML-driven Classification for Round-Robin Sports
+Timetabling", a set of benchmark data instances and results of the TTP.
+Here we include some of these TTP instances into our package.
+
+This package does not offer anything useful except for holding the TTP
+files. You can find the documentation and actual classes for solving and
+playing around with the TSP in package :mod:`~moptipyapps.ttp`.
+
+The original data of `robinX` can be found at
+<https://robinxval.ugent.be/RobinX/travelRepo.php>
+"""
+
+from importlib import resources  # nosem
+from typing import TextIO, cast
+
+from pycommons.io.path import UTF8
+
+
+
+[docs] +def open_resource_stream(file_name: str) -> TextIO: + """ + Open a RobinX resource stream. + + :param file_name: the file name of the resource + :return: the stream + """ + return cast(TextIO, resources.files(__package__).joinpath( + file_name).open("r", encoding=UTF8))
+ +
diff --git a/_sources/.nojekyll b/_sources/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_sources/README.md.txt b/_sources/README.md.txt new file mode 100644 index 00000000..fc2aef10 --- /dev/null +++ b/_sources/README.md.txt @@ -0,0 +1,326 @@ +[![build](https://github.com/thomasWeise/moptipyapps/actions/workflows/build.yml/badge.svg)](https://github.com/thomasWeise/moptipy/actions/workflows/build.yml) +[![pypi version](https://img.shields.io/pypi/v/moptipyapps)](https://pypi.org/project/moptipyapps) +[![pypi downloads](https://img.shields.io/pypi/dw/moptipyapps.svg)](https://pypistats.org/packages/moptipyapps) +[![coverage report](https://thomasweise.github.io/moptipyapps/tc/badge.svg)](https://thomasweise.github.io/moptipyapps/tc/index.html) + +# 1. Introduction + +[`moptipy`](https://thomasweise.github.io/moptipy/) is a library with implementations of metaheuristic optimization methods in Python 3.12 that also offers an environment for replicable experiments ([`flyer`](https://thomasweise.github.io/moptipy/_static/moptipy_flyer.pdf)). +[`moptipyapps`](./) is a collection of applications and experiments based on `moptipy`. + + +# 2. Installation + +In order to use this package and to, e.g., run the example codes, you need to first install it using [`pip`](https://pypi.org/project/pip/) or some other tool that can install packages from [PyPi](https://pypi.org). +You can install the newest version of this library from [PyPi](https://pypi.org/project/moptipyapps/) using [`pip`](https://pypi.org/project/pip/) by doing + +```shell +pip install moptipyapps +``` + +This will install the latest official release of our package as well as [all dependencies](./requirements_txt.html). +If you want to install the latest source code version from GitHub (which may not yet be officially released), you can do + +```shell +pip install git+https://github.com/thomasWeise/moptipyapps.git +``` + +If you want to install the latest source code version from GitHub (which may not yet be officially released) and you have set up a private/public key for GitHub, you can also do: + +```shell +git clone ssh://git@github.com/thomasWeise/moptipyapps +pip install moptipyapps +``` + +This may sometimes work better if you are having trouble reaching GitHub via `https` or `http`. + +You can also clone the repository and then run a [`make` build](./Makefile.html), which will automatically install all dependencies, run all the tests, and then install the package on your system, too. +This will work only on Linux, though. +It also installs the [dependencies for building](./requirements-dev_txt.html), which include, e.g., those for [unit testing and static analysis](#unit-tests-and-static-analysis). +If this build completes successful, you can be sure that [`moptipyapps`](./) will work properly on your machine. + +All dependencies for using and running `moptipyapps` are listed at [here](./requirements_txt.html). +The additional dependencies for a [full `make` build](./Makefile.html), including unit tests, static analysis, and the generation of documentation are listed [here](./requirements-dev_txt.html). + + +# 3. Applications +Here we list the applications of [`moptipy`](https://thomasweise.github.io/moptipy). +Step by step, we will add more and more interesting optimization problems. +For each problem, we provide means to load the problem instances and a basic infrastructure to construct optimization algorithms for solving them. + + +## 3.1. Two-Dimensional Bin Packing Problem +In the package [`moptipyapps.binpacking2d`](./moptipyapps.binpacking2d.html#module-moptipyapps.binpacking2d), we provide tools for experimenting and playing around with the two-dimensional bin packing problem. +Bin packing is a classical domain from Operations Research. +The goal is to pack objects into containers, the so-called bins. +We address [two-dimensional rectangular bin packing](./moptipyapps.binpacking2d.html#module-moptipyapps.binpacking2d). +We provide the bin packing [instances](./moptipyapps.binpacking2d.html#module-moptipyapps.binpacking2d.instance) from [2DPackLib](https://site.unibo.it/operations-research/en/research/2dpacklib) as [resources](./moptipyapps.binpacking2d.html#moptipyapps.binpacking2d.instance.Instance.from_resource) together with [this package](./moptipyapps.binpacking2d.html#module-moptipyapps.binpacking2d) as well as the four non-trivial "[Almost Squares in Almost Squares](https://hdl.handle.net/11245/1.545914)" instances. +Each such instances defines a set of `n_different_items` objects `Oi` with `i` from `1..n_different_objects`. +Each object `Oi` is a rectangle with a given width and height. +The object occur is a given multiplicity `repetitions(O_i)`, i.e., either only once or multiple times. +The bins are rectangles with a given width and height too. +The goal of tackling such an instance is to package all the objects into as few as possible bins. +The objects therefore may be rotated by 90 degrees. + +We address this problem by representing a packing as a [signed permutation with repetitions](https://thomasweise.github.io/moptipy/moptipy.spaces.html#module-moptipy.spaces.signed_permutations) of the numbers `1..n_different_objects`, where the number `i` occurs `repetitions(O_i)` times. +If an object is to be placed in a rotated way, this is denoted by using `-i` instead of `i`. +Such permutations are processed from beginning to end, placing the objects into bins as they come according to some heuristic encoding. +We provide two variants of the Improved Bottom Left encoding. +[The first one](./moptipyapps.binpacking2d.encodings.html#module-moptipyapps.binpacking2d.encodings.ibl_encoding_1) closes bins as soon as one object cannot be placed into them. +[The second one](./moptipyapps.binpacking2d.encodings.html#module-moptipyapps.binpacking2d.encodings.ibl_encoding_2) tries to put each object in the earliest possible bin. +While the former one is faster, the latter one leads to better packings. + +We can then apply a black-box metaheuristic to search in the space of these signed permutations with repetitions. +The objective function would be some measure consistent with the goal of minimizing the number of bins used. + +*Examples:* +- [plot a packing chart](./examples/binpacking2d_plot_py.html) +- [apply a randomized local search to one 2D bin packing instance](./examples/binpacking2d_rls_py.html) +- [measure the runtime of the different encodings for the 2D bin packing problem](./examples/binpacking2d_timing_py.html) + +Important work on this code has been contributed by Mr. Rui ZHAO (赵睿), , a Master's student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥学院) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授), who then refined the implementations. + + +## 3.2. The Traveling Salesperson Problem (TSP) +In the package [`moptipyapps.tsp`](./moptipyapps.tsp.html#module-moptipyapps.tsp), we provide tools to run experiments and play around with the Traveling Salesperson Problem (TSP) . +A TSP instance is defined as a fully-connected graph with `n_cities` nodes. +Each edge in the graph has a weight, which identifies the distance between the nodes. +The goal is to find the *shortest* tour that visits every single node in the graph exactly once and then returns back to its starting node. +Then nodes are usually called cities. +A tour can be represented in path representation, which means that it is stored as a permutation of the numbers `0` to `n_cities-1`. +The number at index `k` identifies that `k`-th city to visit. +So the first number in the permutation identifies the first city, the second number the second city, +and so on. +The length of the tour can be computed by summing up the distances from the `k`-th city to the `k+1`-st city, for `k` in `0..n_cities-2` and then adding the distance from the last city to the first city. + +We use the TSP instances from [TSPLib](http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/), the maybe most important benchmark set for the TSP. +110 of these instances are included as resources in this package. + +*Examples:* +- [apply a randomized local search to one TSP instance](./examples/tsp_rls_py.html) +- [apply a some FFA-based algorithms to one TSP instance](./examples/tsp_special_algorithms_py.html) + +Important work on this code has been contributed by Mr. Tianyu LIANG (梁天宇), a Master's student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥学院) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授). + +The Traveling Tournament Problem ([TTP](#the-traveling-tournament-problem-ttp)) is related to the TTP. + + +## 3.3. Dynamic Controller Synthesis +Another interesting example for optimization is the synthesis of [active controllers for dynamic systems](./moptipyapps.dynamic_control.html). +Dynamic systems have a state that changes over time based on some laws. +These laws may be expressed as ordinary differential equations, for example. +The classical [Stuart-Landau system](./moptipyapps.dynamic_control.systems.html#module-moptipyapps.dynamic_control.systems.stuart_landau), for instance, represents an object whose coordinates on a two-dimensional plane change as follows: + +``` +sigma = 0.1 - x² - y² +dx/dt = sigma * x - y +dy/dt = sigma * y + x +``` + +Regardless on which `(x, y)` the object initially starts, it tends to move to a circular rotation path centered around the origin with radius `sqrt(0.1)`. +Now we try to create a controller `ctrl` for such a system that moves the object from this periodic circular path into a fixed and stable location. +The controller `ctrl` receives the current state, i.e., the object location, as input and can influence the system as follows: + +``` +sigma = 0.1 - x² - y² +c = ctrl(x, y) +dx/dt = sigma * x - y +dy/dt = sigma * y + x + c +``` + +What we try to find is the controller which can bring move object to the origin `(0, 0)` as quickly as possible while expending the least amount of force, i.e., having the smallest aggregated `c` values over time. + + +## 3.4. The Traveling Tournament Problem (TTP) +In the package [`moptipyapps.ttp`](./moptipyapps.ttp.html#module-moptipyapps.ttp), we provide a set of classes and tools to explore the *Traveling Tournament Problem (TTP)*. +In a TTP, we have an even number of `n` teams. +Each team plays a tournament against every other team. +If the tournament is a single round-robin tournament, each team plays exactly once against every other team. +In the more common double round-robin tournament, each team plays twice against every other team — once at home and once at the place of the other team. +A tournament takes `(n - 1) * rounds` days in total, where `rounds = 2` for double round-robin. +Now additionally to the basic constraints dictated by logic (if team `A` plays at home against team `B` on day `d`, then team `B` has an "away" game against team `A` on that day `d` and so on), there are also additional constraints. +For instance, no team should play a continuous streak of home (or away) games longer than `m` days, where `m` usually is `m = 3`. +Also, if teams `A` and `B` play against each other, then there must be at least `p` games in between before they play each other again, usually with `p = 1`. + +Now the first hurdle is to find a game plan that has `n / 2` games on each day (since there are `n` teams and each plays against one other team) that satisfies the above constraints. +The second problem is that this is not all: +For each TTP, a distance matrix is defined, very much like for the [TSP](#the-traveling-salesperson-problem-tsp). +The goal is to find a feasible game schedule where the overall travel distances are minimal. + +*Examples:* +- [apply a local search to find a feasible TTP plan](./examples/ttp_example_experiment_rls_rs_py.html) +- [tackle the TTP in a multi-objective manner](./examples/ttp_example_experiment_mo_py.html) + + +## 3.5. The Quadratic Assignment Problem (QAP) +In the package [`moptipyapps.qap`](./moptipyapps.qap.html#module-moptipyapps.qap), we implement some utilities to play with the Quadratic Assignment Problem (QAP). +The QAP is one of the very classical problems from Operations Research. +Imagine you are planning the layout for a factory. +The goal is to assign `n` facilities (say, machines or workshops) to `n` locations. +Now between the facilities, there exists a flow of goods. +The output of one facility may be the input of another one and vice versa. +The amount of stuff to be transported is likely to be different between different facilities. +Between some facilities, a lot of things may need to be transport. +Between others, there could be no exchange of material or only very little. +The available locations also have different distances among each other. +Some locations are closer, some are farther from each other. +The goal is to find an assignment of facilities to locations such that the overall sum of the product of flow and distance for each facility pair gets minimized. +To this end, solutions can be represented as permutations of facilities determining the order in which they are placed on the locations `1` to `n`. + +*Examples:* +- See [one-dimensional ordering](#one-dimensional-ordering) +- [run a QAP experiment with RLS and random sampling](./examples/qap_example_experiment_rls_rs_py.html) + + +## 3.6. One-Dimensional Ordering +In the package [`moptipyapps.order1d`](./moptipyapps.order1d.html#module-moptipyapps.order1d), we implement what I would like to call the "one-dimensional ordering problem". +Imagine that you have `n` objects and you only know the distances between them. +You want to arrange these objects on one axis, e.g., along the horizontal (`x`) axis, i.e., in a one-dimensional space. +Now what you care about is to reflect the neighborhood structure among the objects (as defined by the distance matrix that you got) to the one-dimensional space. +So the closest neighbor of a given object based on the distance matrix should also be the closest neighbor on the one-dimensional axis. + +The goal of solving this problem is thus to arrange the `n` objects on a 1-dimensional (e.g., horizontal) axis given a distance matrix describing (maybe derived from their location in a potentially high-dimensional or unstructured space). +The objects should be arranged in such a way that, for each object, + +- the nearest neighbors on the 1-dimensional axis are also the nearest neighbors in the original space (according to the distance matrix provided), +- the second nearest neighbors on the 1-dimensional axis are also the second nearest neighbors in the original space (according to the distance matrix provided), +- the third nearest neighbors on the 1-dimensional axis are also the third nearest neighbors in the original space (according to the distance matrix provided), +- and so on; with (quadratically) decreasing weights of neighbor distance ranks. + +The original distances be limited to integers for the sake of simplicity, but we may use floats as well if we want to. +Either way, we do not care about the actual precise distances (e.g., something like "0.001") between the objects on either the one-dimensional nor the original space. +Only about the distance ranks, i.e., about "2nd nearest neighbor," but not "0.012 distance units away." +The solutions of this problem are thus permutations (orders) of the objects. +Of course, if we really want to plot the objects, such a permutation can easily be translated to `x`-coordinates, say, by dividing the index of an object by the number of objects, which nets values in `[0,1]`. +But basically, we reduce the task to finding permutations of objects that reflect the neighbor structure of the original space as closely as possible. + +If such a problem is solved correctly, then the arrangement on the one-dimensional axis should properly reflect the arrangement of the objects in the original space. +Of course, solving this problem exactly may not actually be possible, since an object on a one-dimensional axis may either have exactly two `i`-nearest-neighbors (if it is at least `i` slots away from either end of the permutation) or exactly `1` such neighbor, if it is closer that `i` units. +The object directly at the start of the permutation has only 1 nearest neighbor (the object that comes next). +That next object, however, has two, namely the first object and the third object. +In the original space where the objects come from, however, there may be any number of "nearest neighbors." +Imagine a two-dimensional space where one object sits at the center of a circle of other objects. +Then all other objects are its nearest neighbors, whereas an object on the circle either has exactly two nearest neighbors or, maybe, in the odd situation that the radius equals a multiple of the distance to the neighbors on the circle, three. +Such a structure cannot be represented exactly in one dimension. + +But that's OK. +Because we mainly do this for visualization purposes anyway. + +*Examples:* +- [use one-dimensional ordering to for search trajectory charts](./examples/order1_from_dat_py.html) + + +# 4. Unit Tests and Static Analysis +When developing and applying randomized algorithms, proper testing and checking of the source code is of utmost importance. +If we apply a randomized metaheuristic to an optimization problem, then we usually do not which solution quality we can achieve. +Therefore, we can usually not know whether we have implemented the algorithm correctly. +In other words, detecting bugs is very hard. +Unfortunately, this holds also for the components of the algorithms, such as the search operators, especially if they are randomized as well. +A bug may lead to worse results and we might not even notice that the worse result quality is caused by the bug. +We may think that the algorithm is just not working well on the problem. + +Therefore, we need to test all components of the algorithm as far as we can. +We can try check, for example, if a randomized nullary search operator indeed creates different solutions when invoked several times. +We can try to check whether an algorithm fails with an exception. +We can try to check whether the search operators create valid solutions and whether the algorithm passes valid solutions to the objective function. +We can try to whether an objective function produces finite objective values and if bounds are specified for the objective values, we can check whether they indeed fall within these bounds. +Now we cannot prove that there are no such bugs, due to the randomization. +But by testing a few hundred times, we can at least detect very obvious and pathological bugs. + +To ease such testing for you, we provide a set of tools for testing implemented algorithms, spaces, and operators in the package [moptipyapps.tests](./moptipyapps.tests.html). +Here, you can find functions where you pass in instances of your implemented components and they are checked for compliance with the [moptipy API](https://thomasweise.github.io/moptipy/moptipy.api.html) and the problem setups defined in `moptipyapps`. +In other words, if you go and implement your own algorithms, operators, and optimization problems, you can use our pre-defined unit tests to give them a thorough check before using them in production. +Again, such tests cannot prove the absence of bugs. +But they can at least give you a fair shot to detect pathological errors before wasting serious experimentation time. + +We also try to extensively test our own code, see the coverage report of [`moptipy`](https://thomasweise.github.io/moptipy/tc/index.html) and [`moptipyapps`](./tc/index.html). + +Another way to try to improve and maintain code quality is to use static code analysis and type hints where possible and reasonable. +A static analysis tool can inform you about, e.g., unused variables, which often result from a coding error. +It can tell you if the types of expressions do not match, which usually indicates a coding error, too. +It can tell you if you perform some security-wise unsafe operations (which is less often a problem in optimization, but it does not hurt to check). +Code analysis tools can also help you to enforce best practices, which are good for performance, readability, and maintainability. +They can push you to properly format and document your code, which, too, improve readability, maintainability, and usability. +They even can detect a set of well-known and frequently-occurring bugs. +We therefore also run a variety of such tools on our code base, including (in alphabetical order): + +- [`autoflake`](https://pypi.org/project/autoflake/), a tool for finding unused imports and variables +- [`bandit`](https://pypi.org/project/bandit/), a linter for finding security issues +- [`dodgy`](https://pypi.org/project/dodgy/), for checking for dodgy looking values in the code +- [`flake8`](https://pypi.org/project/flake8/), a collection of linters +- [`flake8-bugbear`](http://pypi.org/project/flake8-bugbear), for finding common bugs +- [`flake8-eradicate`](http://pypi.org/project/flake8-eradicate), for finding commented-out code +- [`flake8-use-fstring`](http://pypi.org/project/flake8-use-fstring), for checking the correct use of f-strings +- [`mypy`](https://pypi.org/project/mypy/), for checking types and type annotations +- [`pycodestyle`](https://pypi.org/project/pycodestyle/), for checking the formatting and coding style of the source +- [`pydocstyle`](https://pypi.org/project/pydocstyle/), for checking the format of the docstrings +- [`pyflakes`](https://pypi.org/project/pyflakes/), for detecting some errors in the code +- [`pylint`](https://pypi.org/project/pylint/), another static analysis tool +- [`pyroma`](https://pypi.org/project/pyroma/), for checking whether the code complies with various best practices +- [`ruff`](https://pypi.org/project/ruff/), a static analysis tool checking a wide range of coding conventions +- [`semgrep`](https://pypi.org/project/semgrep/), another static analyzer for finding bugs and problems +- [`tryceratops`](https://pypi.org/project/tryceratops/), for checking against exception handling anti-patterns +- [`unimport`](https://pypi.org/project/unimport/), for checking against unused import statements +- [`vulture`](https://pypi.org/project/vulture/), for finding dead code + +On git pushes, GitHub also automatically runs [CodeQL](https://codeql.github.com/) to check for common vulnerabilities and coding errors. +We also turned on GitHub's [private vulnerability reporting](https://docs.github.com/en/code-security/security-advisories/repository-security-advisories/configuring-private-vulnerability-reporting-for-a-repository) and the Dependabot [vulnerability](https://docs.github.com/en/code-security/dependabot/dependabot-alerts/configuring-dependabot-alerts) and [security](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates) alerts. + +Using all of these tools increases the build time. +However, combined with thorough unit testing and documentation, it should help to prevent bugs, to improve readability, maintainability, and usability of the code. +It does not matter whether we are doing research or try to solve practical problems in the industry — we should always strive to make good software with high code quality. + +Often, researchers in particular think that hacking something together that works is enough, that documentation is unimportant, that code style best practices can be ignored, and so on. +And then they wonder why they cannot understand their own code a few years down the line (at least, this happened to me in the past…). +Or why no one can use their code to build atop of their research (which is the normal case for me). + +Improving code quality can *never* come later. +We *always* must maintain high coding and documentation standards from the very beginning. +While `moptipy` may still be far from achieving these goals, at least we try to get there. + +Anyway, you can find our full build script running all the tests, doing all the static analyses, creating the documentation, and creating and packaging the distribution files in the repository, too. +Besides the [basic `moptipyapps` dependencies](./requirements-dev_txt.html), it requires [a set of additional dependencies](./requirements-dev_txt.html). +These are all automatically installed during the build procedure. +The build only works under Linux. + + +# 5. License +[`moptipyapps`](./) is a library for implementing, using, and experimenting with metaheuristic optimization algorithms. +Our project is developed for scientific, educational, and industrial applications. + +Copyright (C) 2023 [Thomas Weise](http://iao.hfuu.edu.cn/5) (汤卫思教授) + +Dr. Thomas Weise (see [Contact](#contact)) holds the copyright of this package *except* for the data of the benchmark sets we imported from other sources. +`moptipyapps` is provided to the public as open source software under the [GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007](./LICENSE.html). +Terms for other licenses, e.g., for specific industrial applications, can be negotiated with Dr. Thomas Weise (who can be reached via the [contact information](#contact) below). + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +You should have received a copy of the GNU General Public License along with this program. +If not, see . + +Please visit the [contributions guidelines](./CONTRIBUTING_md.html) for `moptipy` if you would like to contribute to our package. +If you have any concerns regarding security, please visit our [security policy](./SECURITY_md.html). + + +## 5.1. Exceptions +- Most of the included benchmark instance data of the [two-dimensional bin packing problem](#two-dimensional-bin-packing-problem) is taken from [2DPackLib](https://site.unibo.it/operations-research/en/research/2dpacklib). + It has been stored in a more size-efficient way and some unnecessary information has been stripped from it (as we really only need the raw bin packing data). + Nevertheless, the copyright of the original data lies with the authors [2DPackLib](https://site.unibo.it/operations-research/en/research/2dpacklib) or the original authors of the datasets used by them. +- The included benchmark instances for the [Traveling Salesperson Problem](#the-traveling-salesperson-problem-tsp) are taken from [TSPLib](http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/). + The copyright of the original data lies with Gerhard Reinelt, the original author of TSPLib, or the original authors of the datasets used by him. +- The included benchmark instances for the [Traveling Tournament Problem](./#the-traveling-tournament-problem-ttp) are taken from [RobinX](https://robinxval.ugent.be/RobinX/travelRepo.php). + The copyright of the original data lies with the [authors](https://robinxval.ugent.be/RobinX/contact.php) of the dataset, presumably D. Van Bulck, D. Goossens, J. Schönberger, and M. Guajardo. +- The included benchmark instances for the Quadratic Assignment Problem are taken from QAPLib, which is available at and +. + The copyright of the original repository lies with R.E. Burkard, E. Çela, S.E. Karisch and F. Rendl as well as Peter Hahn and Miguel Anjos. + + +# 6. Contact +If you have any questions or suggestions, please contact +Prof. Dr. [Thomas Weise](http://iao.hfuu.edu.cn/5) (汤卫思教授) of the +Institute of Applied Optimization (应用优化研究所, [IAO](http://iao.hfuu.edu.cn)) of the +School of Artificial Intelligence and Big Data ([人工智能与大数据学院](http://www.hfuu.edu.cn/aibd/)) at +[Hefei University](http://www.hfuu.edu.cn/english/) ([合肥大学](http://www.hfuu.edu.cn/)) in +Hefei, Anhui, China (中国安徽省合肥市) via +email to [tweise@hfuu.edu.cn](mailto:tweise@hfuu.edu.cn) with CC to [tweise@ustc.edu.cn](mailto:tweise@ustc.edu.cn). diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 00000000..e1ef6b96 --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,17 @@ +moptipyapps: Applications of Metaheuristic Optimization in Python +================================================================= + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + +.. include:: README.md + :parser: myst_parser.sphinx_ + +7. Modules and Code +------------------- + +.. toctree:: + :maxdepth: 4 + + modules diff --git a/_sources/modules.rst.txt b/_sources/modules.rst.txt new file mode 100644 index 00000000..ae7a9a31 --- /dev/null +++ b/_sources/modules.rst.txt @@ -0,0 +1,7 @@ +moptipyapps +=========== + +.. toctree:: + :maxdepth: 4 + + moptipyapps diff --git a/_sources/moptipyapps.binpacking2d.encodings.rst.txt b/_sources/moptipyapps.binpacking2d.encodings.rst.txt new file mode 100644 index 00000000..7afb31d1 --- /dev/null +++ b/_sources/moptipyapps.binpacking2d.encodings.rst.txt @@ -0,0 +1,26 @@ +moptipyapps.binpacking2d.encodings package +========================================== + +.. automodule:: moptipyapps.binpacking2d.encodings + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +moptipyapps.binpacking2d.encodings.ibl\_encoding\_1 module +---------------------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.encodings.ibl_encoding_1 + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.encodings.ibl\_encoding\_2 module +---------------------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.encodings.ibl_encoding_2 + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.binpacking2d.instgen.rst.txt b/_sources/moptipyapps.binpacking2d.instgen.rst.txt new file mode 100644 index 00000000..a6bf19b8 --- /dev/null +++ b/_sources/moptipyapps.binpacking2d.instgen.rst.txt @@ -0,0 +1,66 @@ +moptipyapps.binpacking2d.instgen package +======================================== + +.. automodule:: moptipyapps.binpacking2d.instgen + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +moptipyapps.binpacking2d.instgen.errors module +---------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.instgen.errors + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.instgen.errors\_and\_hardness module +------------------------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.instgen.errors_and_hardness + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.instgen.experiment module +-------------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.instgen.experiment + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.instgen.hardness module +------------------------------------------------ + +.. automodule:: moptipyapps.binpacking2d.instgen.hardness + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.instgen.inst\_decoding module +------------------------------------------------------ + +.. automodule:: moptipyapps.binpacking2d.instgen.inst_decoding + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.instgen.instance\_space module +------------------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.instgen.instance_space + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.instgen.problem module +----------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.instgen.problem + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.binpacking2d.objectives.rst.txt b/_sources/moptipyapps.binpacking2d.objectives.rst.txt new file mode 100644 index 00000000..37b362bd --- /dev/null +++ b/_sources/moptipyapps.binpacking2d.objectives.rst.txt @@ -0,0 +1,66 @@ +moptipyapps.binpacking2d.objectives package +=========================================== + +.. automodule:: moptipyapps.binpacking2d.objectives + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +moptipyapps.binpacking2d.objectives.bin\_count module +----------------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.objectives.bin_count + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.objectives.bin\_count\_and\_empty module +----------------------------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.objectives.bin_count_and_empty + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.objectives.bin\_count\_and\_last\_empty module +----------------------------------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.objectives.bin_count_and_last_empty + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.objectives.bin\_count\_and\_last\_skyline module +------------------------------------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.objectives.bin\_count\_and\_last\_small module +----------------------------------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.objectives.bin_count_and_last_small + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.objectives.bin\_count\_and\_lowest\_skyline module +--------------------------------------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.objectives.bin\_count\_and\_small module +----------------------------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.objectives.bin_count_and_small + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.binpacking2d.rst.txt b/_sources/moptipyapps.binpacking2d.rst.txt new file mode 100644 index 00000000..452ffb2f --- /dev/null +++ b/_sources/moptipyapps.binpacking2d.rst.txt @@ -0,0 +1,92 @@ +moptipyapps.binpacking2d package +================================ + +.. automodule:: moptipyapps.binpacking2d + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + moptipyapps.binpacking2d.encodings + moptipyapps.binpacking2d.instgen + moptipyapps.binpacking2d.objectives + +Submodules +---------- + +moptipyapps.binpacking2d.bks module +----------------------------------- + +.. automodule:: moptipyapps.binpacking2d.bks + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.experiment module +------------------------------------------ + +.. automodule:: moptipyapps.binpacking2d.experiment + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.instance module +---------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.instance + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.make\_instances module +----------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.make_instances + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.packing module +--------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.packing + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.packing\_result module +----------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.packing_result + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.packing\_space module +---------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.packing_space + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.packing\_statistics module +--------------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.packing_statistics + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.binpacking2d.plot\_packing module +--------------------------------------------- + +.. automodule:: moptipyapps.binpacking2d.plot_packing + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.dynamic_control.controllers.rst.txt b/_sources/moptipyapps.dynamic_control.controllers.rst.txt new file mode 100644 index 00000000..e727b95f --- /dev/null +++ b/_sources/moptipyapps.dynamic_control.controllers.rst.txt @@ -0,0 +1,82 @@ +moptipyapps.dynamic\_control.controllers package +================================================ + +.. automodule:: moptipyapps.dynamic_control.controllers + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +moptipyapps.dynamic\_control.controllers.ann module +--------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.controllers.ann + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.controllers.codegen module +------------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.controllers.codegen + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.controllers.cubic module +----------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.controllers.cubic + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.controllers.linear module +------------------------------------------------------ + +.. automodule:: moptipyapps.dynamic_control.controllers.linear + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.controllers.min\_ann module +-------------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.controllers.min_ann + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.controllers.partially\_linear module +----------------------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.controllers.partially_linear + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.controllers.peaks module +----------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.controllers.peaks + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.controllers.predefined module +---------------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.controllers.predefined + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.controllers.quadratic module +--------------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.controllers.quadratic + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.dynamic_control.rst.txt b/_sources/moptipyapps.dynamic_control.rst.txt new file mode 100644 index 00000000..cc13485b --- /dev/null +++ b/_sources/moptipyapps.dynamic_control.rst.txt @@ -0,0 +1,123 @@ +moptipyapps.dynamic\_control package +==================================== + +.. automodule:: moptipyapps.dynamic_control + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + moptipyapps.dynamic_control.controllers + moptipyapps.dynamic_control.systems + +Submodules +---------- + +moptipyapps.dynamic\_control.controller module +---------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.controller + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.experiment\_raw module +--------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.experiment_raw + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.experiment\_surrogate module +--------------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.experiment_surrogate + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.instance module +-------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.instance + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.model\_objective module +---------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.model_objective + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.objective module +--------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.objective + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.ode module +--------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.ode + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.results\_log module +------------------------------------------------ + +.. automodule:: moptipyapps.dynamic_control.results_log + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.results\_plot module +------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.results_plot + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.starting\_points module +---------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.starting_points + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.surrogate\_optimizer module +-------------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.surrogate_optimizer + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.system module +------------------------------------------ + +.. automodule:: moptipyapps.dynamic_control.system + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.system\_model module +------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.system_model + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.dynamic_control.systems.rst.txt b/_sources/moptipyapps.dynamic_control.systems.rst.txt new file mode 100644 index 00000000..55543c3b --- /dev/null +++ b/_sources/moptipyapps.dynamic_control.systems.rst.txt @@ -0,0 +1,34 @@ +moptipyapps.dynamic\_control.systems package +============================================ + +.. automodule:: moptipyapps.dynamic_control.systems + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +moptipyapps.dynamic\_control.systems.lorenz module +-------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.systems.lorenz + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.systems.stuart\_landau module +---------------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.systems.stuart_landau + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.dynamic\_control.systems.three\_coupled\_oscillators module +----------------------------------------------------------------------- + +.. automodule:: moptipyapps.dynamic_control.systems.three_coupled_oscillators + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.order1d.rst.txt b/_sources/moptipyapps.order1d.rst.txt new file mode 100644 index 00000000..9494b275 --- /dev/null +++ b/_sources/moptipyapps.order1d.rst.txt @@ -0,0 +1,34 @@ +moptipyapps.order1d package +=========================== + +.. automodule:: moptipyapps.order1d + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +moptipyapps.order1d.distances module +------------------------------------ + +.. automodule:: moptipyapps.order1d.distances + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.order1d.instance module +----------------------------------- + +.. automodule:: moptipyapps.order1d.instance + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.order1d.space module +-------------------------------- + +.. automodule:: moptipyapps.order1d.space + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.qap.qaplib.rst.txt b/_sources/moptipyapps.qap.qaplib.rst.txt new file mode 100644 index 00000000..21727ce3 --- /dev/null +++ b/_sources/moptipyapps.qap.qaplib.rst.txt @@ -0,0 +1,7 @@ +moptipyapps.qap.qaplib package +============================== + +.. automodule:: moptipyapps.qap.qaplib + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.qap.rst.txt b/_sources/moptipyapps.qap.rst.txt new file mode 100644 index 00000000..73833272 --- /dev/null +++ b/_sources/moptipyapps.qap.rst.txt @@ -0,0 +1,34 @@ +moptipyapps.qap package +======================= + +.. automodule:: moptipyapps.qap + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + moptipyapps.qap.qaplib + +Submodules +---------- + +moptipyapps.qap.instance module +------------------------------- + +.. automodule:: moptipyapps.qap.instance + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.qap.objective module +-------------------------------- + +.. automodule:: moptipyapps.qap.objective + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.rst.txt b/_sources/moptipyapps.rst.txt new file mode 100644 index 00000000..4a0fd6bb --- /dev/null +++ b/_sources/moptipyapps.rst.txt @@ -0,0 +1,40 @@ +moptipyapps package +=================== + +.. automodule:: moptipyapps + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + moptipyapps.binpacking2d + moptipyapps.dynamic_control + moptipyapps.order1d + moptipyapps.qap + moptipyapps.tests + moptipyapps.tsp + moptipyapps.ttp + +Submodules +---------- + +moptipyapps.shared module +------------------------- + +.. automodule:: moptipyapps.shared + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.version module +-------------------------- + +.. automodule:: moptipyapps.version + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.tests.rst.txt b/_sources/moptipyapps.tests.rst.txt new file mode 100644 index 00000000..b2080f3f --- /dev/null +++ b/_sources/moptipyapps.tests.rst.txt @@ -0,0 +1,26 @@ +moptipyapps.tests package +========================= + +.. automodule:: moptipyapps.tests + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +moptipyapps.tests.on\_binpacking2d module +----------------------------------------- + +.. automodule:: moptipyapps.tests.on_binpacking2d + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.tests.on\_tsp module +-------------------------------- + +.. automodule:: moptipyapps.tests.on_tsp + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.tsp.rst.txt b/_sources/moptipyapps.tsp.rst.txt new file mode 100644 index 00000000..9992565c --- /dev/null +++ b/_sources/moptipyapps.tsp.rst.txt @@ -0,0 +1,58 @@ +moptipyapps.tsp package +======================= + +.. automodule:: moptipyapps.tsp + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + moptipyapps.tsp.tsplib + +Submodules +---------- + +moptipyapps.tsp.ea1p1\_revn module +---------------------------------- + +.. automodule:: moptipyapps.tsp.ea1p1_revn + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.tsp.fea1p1\_revn module +----------------------------------- + +.. automodule:: moptipyapps.tsp.fea1p1_revn + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.tsp.instance module +------------------------------- + +.. automodule:: moptipyapps.tsp.instance + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.tsp.known\_optima module +------------------------------------ + +.. automodule:: moptipyapps.tsp.known_optima + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.tsp.tour\_length module +----------------------------------- + +.. automodule:: moptipyapps.tsp.tour_length + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.tsp.tsplib.rst.txt b/_sources/moptipyapps.tsp.tsplib.rst.txt new file mode 100644 index 00000000..8989f42e --- /dev/null +++ b/_sources/moptipyapps.tsp.tsplib.rst.txt @@ -0,0 +1,7 @@ +moptipyapps.tsp.tsplib package +============================== + +.. automodule:: moptipyapps.tsp.tsplib + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.ttp.robinx.rst.txt b/_sources/moptipyapps.ttp.robinx.rst.txt new file mode 100644 index 00000000..3d415aa2 --- /dev/null +++ b/_sources/moptipyapps.ttp.robinx.rst.txt @@ -0,0 +1,7 @@ +moptipyapps.ttp.robinx package +============================== + +.. automodule:: moptipyapps.ttp.robinx + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/moptipyapps.ttp.rst.txt b/_sources/moptipyapps.ttp.rst.txt new file mode 100644 index 00000000..49f0084e --- /dev/null +++ b/_sources/moptipyapps.ttp.rst.txt @@ -0,0 +1,66 @@ +moptipyapps.ttp package +======================= + +.. automodule:: moptipyapps.ttp + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + moptipyapps.ttp.robinx + +Submodules +---------- + +moptipyapps.ttp.errors module +----------------------------- + +.. automodule:: moptipyapps.ttp.errors + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.ttp.game\_encoding module +------------------------------------- + +.. automodule:: moptipyapps.ttp.game_encoding + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.ttp.game\_plan module +--------------------------------- + +.. automodule:: moptipyapps.ttp.game_plan + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.ttp.game\_plan\_space module +---------------------------------------- + +.. automodule:: moptipyapps.ttp.game_plan_space + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.ttp.instance module +------------------------------- + +.. automodule:: moptipyapps.ttp.instance + :members: + :undoc-members: + :show-inheritance: + +moptipyapps.ttp.plan\_length module +----------------------------------- + +.. automodule:: moptipyapps.ttp.plan_length + :members: + :undoc-members: + :show-inheritance: diff --git a/_static/.nojekyll b/_static/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/_static/background_b01.png b/_static/background_b01.png new file mode 100644 index 00000000..353f26dd Binary files /dev/null and b/_static/background_b01.png differ diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 00000000..5e1cb131 --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,914 @@ +/* + * Sphinx stylesheet -- basic theme. + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 210px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin-top: 10px; +} + +ul.search li { + padding: 5px 0; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/bizstyle.css b/_static/bizstyle.css new file mode 100644 index 00000000..2b46ec39 --- /dev/null +++ b/_static/bizstyle.css @@ -0,0 +1,505 @@ +/* + * Sphinx stylesheet -- business style theme. + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', + 'Verdana', sans-serif; + font-size: 14px; + letter-spacing: -0.01em; + line-height: 150%; + text-align: center; + background-color: white; + background-image: url(background_b01.png); + color: black; + padding: 0; + border-right: 1px solid #336699; + border-left: 1px solid #336699; + + margin: 0px 40px 0px 40px; +} + +div.document { + background-color: white; + text-align: left; + background-repeat: repeat-x; + + -moz-box-shadow: 2px 2px 5px #000; + -webkit-box-shadow: 2px 2px 5px #000; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 240px; + border-left: 1px solid #ccc; +} + +div.body { + margin: 0; + padding: 0.5em 20px 20px 20px; +} +div.bodywrapper { + margin: 0 0 0 calc(210px + 30px); +} + +div.related { + font-size: 1em; + + -moz-box-shadow: 2px 2px 5px #000; + -webkit-box-shadow: 2px 2px 5px #000; +} + +div.related ul { + background-color: #336699; + height: 100%; + overflow: hidden; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} + +div.related ul li { + color: white; + margin: 0; + padding: 0; + height: 2em; + float: left; +} + +div.related ul li.right { + float: right; + margin-right: 5px; +} + +div.related ul li a { + margin: 0; + padding: 0 5px 0 5px; + line-height: 1.75em; + color: #fff; +} + +div.related ul li a:hover { + color: #fff; + text-decoration: underline; +} + +div.sphinxsidebarwrapper { + padding: 0; +} + +div.sphinxsidebar { + padding: 0.5em 12px 12px 12px; + width: 210px; + font-size: 1em; + text-align: left; +} + +div.sphinxsidebar h3, div.sphinxsidebar h4 { + margin: 1em 0 0.5em 0; + font-size: 1em; + padding: 0.1em 0 0.1em 0.5em; + color: white; + border: 1px solid #336699; + background-color: #336699; +} + +div.sphinxsidebar h3 a { + color: white; +} + +div.sphinxsidebar ul { + padding-left: 1.5em; + margin-top: 7px; + padding: 0; + line-height: 130%; +} + +div.sphinxsidebar ul ul { + margin-left: 20px; +} + +div.sphinxsidebar input { + border: 1px solid #336699; +} + +div.footer { + background-color: white; + color: #336699; + padding: 3px 8px 3px 0; + clear: both; + font-size: 0.8em; + text-align: right; + border-bottom: 1px solid #336699; + + -moz-box-shadow: 2px 2px 5px #000; + -webkit-box-shadow: 2px 2px 5px #000; +} + +div.footer a { + color: #336699; + text-decoration: underline; +} + +/* -- body styles ----------------------------------------------------------- */ + +p { + margin: 0.8em 0 0.5em 0; +} + +a { + color: #336699; + text-decoration: none; +} + +a:hover { + color: #336699; + text-decoration: underline; +} + +a:visited { + color: #551a8b; +} + +div.body a { + text-decoration: underline; +} + +h1, h2, h3 { + color: #336699; +} + +h1 { + margin: 0; + padding: 0.7em 0 0.3em 0; + font-size: 1.5em; +} + +h2 { + margin: 1.3em 0 0.2em 0; + font-size: 1.35em; + padding-bottom: .5em; + border-bottom: 1px solid #336699; +} + +h3 { + margin: 1em 0 -0.3em 0; + font-size: 1.2em; + padding-bottom: .3em; + border-bottom: 1px solid #CCCCCC; +} + +div.body h1 a, div.body h2 a, div.body h3 a, +div.body h4 a, div.body h5 a, div.body h6 a { + color: black!important; +} + +h1 a.anchor, h2 a.anchor, h3 a.anchor, +h4 a.anchor, h5 a.anchor, h6 a.anchor { + display: none; + margin: 0 0 0 0.3em; + padding: 0 0.2em 0 0.2em; + color: #aaa!important; +} + +h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, +h5:hover a.anchor, h6:hover a.anchor { + display: inline; +} + +h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, +h5 a.anchor:hover, h6 a.anchor:hover { + color: #777; + background-color: #eee; +} + +a.headerlink { + color: #c60f0f!important; + font-size: 1em; + margin-left: 6px; + padding: 0 4px 0 4px; + text-decoration: none!important; +} + +a.headerlink:hover { + background-color: #ccc; + color: white!important; +} + +cite, code, tt { + font-family: 'Consolas', 'Deja Vu Sans Mono', + 'Bitstream Vera Sans Mono', monospace; + font-size: 0.95em; + letter-spacing: 0.01em; +} + +code { + background-color: #F2F2F2; + border-bottom: 1px solid #ddd; + color: #333; +} + +code.descname, code.descclassname, code.xref { + border: 0; +} + +hr { + border: 1px solid #abc; + margin: 2em; +} + +a code { + border: 0; + color: #CA7900; +} + +a code:hover { + color: #2491CF; +} + +pre { + background-color: transparent !important; + font-family: 'Consolas', 'Deja Vu Sans Mono', + 'Bitstream Vera Sans Mono', monospace; + font-size: 0.95em; + letter-spacing: 0.015em; + line-height: 120%; + padding: 0.5em; + border-right: 5px solid #ccc; + border-left: 5px solid #ccc; +} + +pre a { + color: inherit; + text-decoration: underline; +} + +td.linenos pre { + padding: 0.5em 0; +} + +div.quotebar { + background-color: #f8f8f8; + max-width: 250px; + float: right; + padding: 2px 7px; + border: 1px solid #ccc; +} + +nav.contents, +aside.topic, +div.topic { + background-color: #f8f8f8; +} + +table { + border-collapse: collapse; + margin: 0 -0.5em 0 -0.5em; +} + +table td, table th { + padding: 0.2em 0.5em 0.2em 0.5em; +} + +div.admonition { + font-size: 0.9em; + margin: 1em 0 1em 0; + border: 3px solid #cccccc; + background-color: #f7f7f7; + padding: 0; +} + +div.admonition p { + margin: 0.5em 1em 0.5em 1em; + padding: 0; +} + +div.admonition li p { + margin-left: 0; +} + +div.admonition pre, div.warning pre { + margin: 0; +} + +div.highlight { + margin: 0.4em 1em; +} + +div.admonition p.admonition-title { + margin: 0; + padding: 0.1em 0 0.1em 0.5em; + color: white; + border-bottom: 3px solid #cccccc; + font-weight: bold; + background-color: #165e83; +} + +div.danger { border: 3px solid #f0908d; background-color: #f0cfa0; } +div.error { border: 3px solid #f0908d; background-color: #ede4cd; } +div.warning { border: 3px solid #f8b862; background-color: #f0cfa0; } +div.caution { border: 3px solid #f8b862; background-color: #ede4cd; } +div.attention { border: 3px solid #f8b862; background-color: #f3f3f3; } +div.important { border: 3px solid #f0cfa0; background-color: #ede4cd; } +div.note { border: 3px solid #f0cfa0; background-color: #f3f3f3; } +div.hint { border: 3px solid #bed2c3; background-color: #f3f3f3; } +div.tip { border: 3px solid #bed2c3; background-color: #f3f3f3; } + +div.danger p.admonition-title, div.error p.admonition-title { + background-color: #b7282e; + border-bottom: 3px solid #f0908d; +} + +div.caution p.admonition-title, +div.warning p.admonition-title, +div.attention p.admonition-title { + background-color: #f19072; + border-bottom: 3px solid #f8b862; +} + +div.note p.admonition-title, div.important p.admonition-title { + background-color: #f8b862; + border-bottom: 3px solid #f0cfa0; +} + +div.hint p.admonition-title, div.tip p.admonition-title { + background-color: #7ebea5; + border-bottom: 3px solid #bed2c3; +} + +div.admonition ul, div.admonition ol, +div.warning ul, div.warning ol { + margin: 0.1em 0.5em 0.5em 3em; + padding: 0; +} + +div.versioninfo { + margin: 1em 0 0 0; + border: 1px solid #ccc; + background-color: #DDEAF0; + padding: 8px; + line-height: 1.3em; + font-size: 0.9em; +} + +.viewcode-back { + font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', + 'Verdana', sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} + +p.versionchanged span.versionmodified { + font-size: 0.9em; + margin-right: 0.2em; + padding: 0.1em; + background-color: #DCE6A0; +} + +dl.field-list > dt { + color: white; + background-color: #82A0BE; +} + +dl.field-list > dd { + background-color: #f7f7f7; +} + +/* -- table styles ---------------------------------------------------------- */ + +table.docutils { + margin: 1em 0; + padding: 0; + border: 1px solid white; + background-color: #f7f7f7; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 1px solid white; + border-bottom: 1px solid white; +} + +table.docutils td p { + margin-top: 0; + margin-bottom: 0.3em; +} + +table.field-list td, table.field-list th { + border: 0 !important; + word-break: break-word; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + color: white; + text-align: left; + padding-right: 5px; + background-color: #82A0BE; +} + +div.literal-block-wrapper div.code-block-caption { + background-color: #EEE; + border-style: solid; + border-color: #CCC; + border-width: 1px 5px; +} + +/* WIDE DESKTOP STYLE */ +@media only screen and (min-width: 1176px) { +body { + margin: 0 40px 0 40px; +} +} + +/* TABLET STYLE */ +@media only screen and (min-width: 768px) and (max-width: 991px) { +body { + margin: 0 40px 0 40px; +} +} + +/* MOBILE LAYOUT (PORTRAIT/320px) */ +@media only screen and (max-width: 767px) { +body { + margin: 0; +} +div.bodywrapper { + margin: 0; + width: 100%; + border: none; +} +div.sphinxsidebar { + display: none; +} +} + +/* MOBILE LAYOUT (LANDSCAPE/480px) */ +@media only screen and (min-width: 480px) and (max-width: 767px) { +body { + margin: 0 20px 0 20px; +} +} + +/* RETINA OVERRIDES */ +@media +only screen and (-webkit-min-device-pixel-ratio: 2), +only screen and (min-device-pixel-ratio: 2) { +} + +/* -- end ------------------------------------------------------------------- */ \ No newline at end of file diff --git a/_static/bizstyle.js b/_static/bizstyle.js new file mode 100644 index 00000000..82b87b0f --- /dev/null +++ b/_static/bizstyle.js @@ -0,0 +1,24 @@ +/* + * Sphinx javascript -- for bizstyle theme. + * + * This theme was created by referring to 'sphinxdoc' + */ +const initialiseBizStyle = () => { + if (navigator.userAgent.indexOf("iPhone") > 0 || navigator.userAgent.indexOf("Android") > 0) { + document.querySelector("li.nav-item-0 a").innerText = "Top" + } + const truncator = item => {if (item.textContent.length > 20) { + item.title = item.innerText + item.innerText = item.innerText.substr(0, 17) + "..." + } + } + document.querySelectorAll("div.related:first ul li:not(.right) a").slice(1).forEach(truncator); + document.querySelectorAll("div.related:last ul li:not(.right) a").slice(1).forEach(truncator); +} + +window.addEventListener("resize", + () => (document.querySelector("li.nav-item-0 a").innerText = (window.innerWidth <= 776) ? "Top" : "moptipyapps 0.8.62 documentation") +) + +if (document.readyState !== "loading") initialiseBizStyle() +else document.addEventListener("DOMContentLoaded", initialiseBizStyle) \ No newline at end of file diff --git a/_static/css3-mediaqueries.js b/_static/css3-mediaqueries.js new file mode 100644 index 00000000..59735f59 --- /dev/null +++ b/_static/css3-mediaqueries.js @@ -0,0 +1 @@ +if(typeof Object.create!=="function"){Object.create=function(e){function t(){}t.prototype=e;return new t}}var ua={toString:function(){return navigator.userAgent},test:function(e){return this.toString().toLowerCase().indexOf(e.toLowerCase())>-1}};ua.version=(ua.toString().toLowerCase().match(/[\s\S]+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1];ua.webkit=ua.test("webkit");ua.gecko=ua.test("gecko")&&!ua.webkit;ua.opera=ua.test("opera");ua.ie=ua.test("msie")&&!ua.opera;ua.ie6=ua.ie&&document.compatMode&&typeof document.documentElement.style.maxHeight==="undefined";ua.ie7=ua.ie&&document.documentElement&&typeof document.documentElement.style.maxHeight!=="undefined"&&typeof XDomainRequest==="undefined";ua.ie8=ua.ie&&typeof XDomainRequest!=="undefined";var domReady=function(){var e=[];var t=function(){if(!arguments.callee.done){arguments.callee.done=true;for(var t=0;t=200&&r.status<300||r.status===304||navigator.userAgent.indexOf("Safari")>-1&&typeof r.status==="undefined"){t(r.responseText)}else{n()}document.documentElement.style.cursor="";r=null}};r.send("")};var l=function(t){t=t.replace(e.REDUNDANT_COMPONENTS,"");t=t.replace(e.REDUNDANT_WHITESPACE,"$1");t=t.replace(e.WHITESPACE_IN_PARENTHESES,"($1)");t=t.replace(e.MORE_WHITESPACE," ");t=t.replace(e.FINAL_SEMICOLONS,"}");return t};var c={stylesheet:function(t){var n={};var r=[],i=[],s=[],o=[];var u=t.cssHelperText;var a=t.getAttribute("media");if(a){var f=a.toLowerCase().split(",")}else{var f=["all"]}for(var l=0;l-1&&a.href&&a.href.length!==0&&!a.disabled){r[r.length]=a}}if(r.length>0){var c=0;var d=function(){c++;if(c===r.length){i()}};var v=function(t){var n=t.href;f(n,function(r){r=l(r).replace(e.RELATIVE_URLS,"url("+n.substring(0,n.lastIndexOf("/"))+"/$1)");t.cssHelperText=r;d()},d)};for(u=0;u0){r.setAttribute("media",t.join(","))}document.getElementsByTagName("head")[0].appendChild(r);if(r.styleSheet){r.styleSheet.cssText=e}else{r.appendChild(document.createTextNode(e))}r.addedWithCssHelper=true;if(typeof n==="undefined"||n===true){cssHelper.parsed(function(t){var n=p(r,e);for(var i in n){if(n.hasOwnProperty(i)){g(i,n[i])}}a("newStyleParsed",r)})}else{r.parsingDisallowed=true}return r},removeStyle:function(e){return e.parentNode.removeChild(e)},parsed:function(e){if(n){s(e)}else{if(typeof t!=="undefined"){if(typeof e==="function"){e(t)}}else{s(e);d()}}},stylesheets:function(e){cssHelper.parsed(function(t){e(m.stylesheets||y("stylesheets"))})},mediaQueryLists:function(e){cssHelper.parsed(function(t){e(m.mediaQueryLists||y("mediaQueryLists"))})},rules:function(e){cssHelper.parsed(function(t){e(m.rules||y("rules"))})},selectors:function(e){cssHelper.parsed(function(t){e(m.selectors||y("selectors"))})},declarations:function(e){cssHelper.parsed(function(t){e(m.declarations||y("declarations"))})},properties:function(e){cssHelper.parsed(function(t){e(m.properties||y("properties"))})},broadcast:a,addListener:function(e,t){if(typeof t==="function"){if(!u[e]){u[e]={listeners:[]}}u[e].listeners[u[e].listeners.length]=t}},removeListener:function(e,t){if(typeof t==="function"&&u[e]){var n=u[e].listeners;for(var r=0;r=a||s&&l0}}else if("device-height"===e.substring(r-13,r)){c=screen.height;if(t!==null){if(u==="length"){return i&&c>=a||s&&c0}}else if("width"===e.substring(r-5,r)){l=document.documentElement.clientWidth||document.body.clientWidth;if(t!==null){if(u==="length"){return i&&l>=a||s&&l0}}else if("height"===e.substring(r-6,r)){c=document.documentElement.clientHeight||document.body.clientHeight;if(t!==null){if(u==="length"){return i&&c>=a||s&&c0}}else if("device-aspect-ratio"===e.substring(r-19,r)){return u==="aspect-ratio"&&screen.width*a[1]===screen.height*a[0]}else if("color-index"===e.substring(r-11,r)){var h=Math.pow(2,screen.colorDepth);if(t!==null){if(u==="absolute"){return i&&h>=a||s&&h0}}else if("color"===e.substring(r-5,r)){var p=screen.colorDepth;if(t!==null){if(u==="absolute"){return i&&p>=a||s&&p0}}else if("resolution"===e.substring(r-10,r)){var d;if(f==="dpcm"){d=o("1cm")}else{d=o("1in")}if(t!==null){if(u==="resolution"){return i&&d>=a||s&&d0}}else{return false}};var a=function(e){var t=e.getValid();var n=e.getExpressions();var r=n.length;if(r>0){for(var i=0;i0){u=false;for(var f=0;f0){l[c++]=","}l[c++]=h}}if(l.length>0){r[r.length]=cssHelper.addStyle("@media "+l.join("")+"{"+e.getCssText()+"}",t,false)}};var l=function(e,t){for(var n=0;n0}}var o=[],u=[];for(var f in i){if(i.hasOwnProperty(f)){o[o.length]=f;if(i[f]){u[u.length]=f}if(f==="all"){n=true}}}if(u.length>0){r[r.length]=cssHelper.addStyle(e.getCssText(),u,false)}var c=e.getMediaQueryLists();if(n){l(c)}else{l(c,o)}};var h=function(e){for(var t=0;td||Math.abs(s-t)>d){e=n;t=s;clearTimeout(r);r=setTimeout(function(){if(!i()){p()}else{cssHelper.broadcast("cssMediaQueriesTested")}},500)}};window.onresize=function(){var e=window.onresize||function(){};return function(){e();s()}}()};var m=document.documentElement;m.style.marginLeft="-32767px";setTimeout(function(){m.style.marginLeft=""},5e3);return function(){if(!i()){cssHelper.addListener("newStyleParsed",function(e){c(e.cssHelperParsed.stylesheet)});cssHelper.addListener("cssMediaQueriesTested",function(){if(ua.ie){m.style.width="1px"}setTimeout(function(){m.style.width="";m.style.marginLeft=""},0);cssHelper.removeListener("cssMediaQueriesTested",arguments.callee)});s();p()}else{m.style.marginLeft=""}v()}}());try{document.execCommand("BackgroundImageCache",false,true)}catch(e){} diff --git a/_static/css3-mediaqueries_src.js b/_static/css3-mediaqueries_src.js new file mode 100644 index 00000000..8aedaab9 --- /dev/null +++ b/_static/css3-mediaqueries_src.js @@ -0,0 +1,1104 @@ +/* +css3-mediaqueries.js - CSS Helper and CSS3 Media Queries Enabler + +author: Wouter van der Graaf +version: 1.0 (20110330) +license: MIT +website: https://code.google.com/p/css3-mediaqueries-js/ + +W3C spec: https://www.w3.org/TR/css3-mediaqueries/ + +Note: use of embedded

"""The configuration for py.test."""
+
diff --git a/examples/.nojekyll b/examples/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/examples/binpacking2d_plot_py.html b/examples/binpacking2d_plot_py.html new file mode 100644 index 00000000..6e9042d1 --- /dev/null +++ b/examples/binpacking2d_plot_py.html @@ -0,0 +1,113 @@ +

"""
+Plot a 2-dimensional packing.
+
+Here we try out the two different variants of the improved-bottom-left
+encoding. `ImprovedBottomLeftEncoding1` inserts objects one by one and moves
+on to a new bin once an object doesn't fit into the current bin. It will then
+never consider the filled bin again. `ImprovedBottomLeftEncoding2` always
+tries all bins for any object.
+
+We try both encodings for instance `a10` and fixed (but randomly chosen)
+signed permutation as input string.
+
+You will see that `ImprovedBottomLeftEncoding1` needs four bins, whereas
+`ImprovedBottomLeftEncoding2` needs three.
+
+In the file `binpacking2d_rls.py`, we plug `ImprovedBottomLeftEncoding2` into
+a simple randomized local search. This search runs for 1024 steps and finds a
+packing that only needs two bins.
+"""
+from time import sleep
+from webbrowser import open_new_tab
+
+import numpy as np
+from moptipy.utils.nputils import rand_generator
+from moptipy.utils.plot_utils import save_figure
+from pycommons.io.temp import temp_dir
+from pycommons.processes.caller import is_build
+
+from moptipyapps.binpacking2d.encodings.ibl_encoding_1 import (
+    ImprovedBottomLeftEncoding1,
+)
+from moptipyapps.binpacking2d.encodings.ibl_encoding_2 import (
+    ImprovedBottomLeftEncoding2,
+)
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.packing_space import PackingSpace
+from moptipyapps.binpacking2d.plot_packing import plot_packing
+
+random = rand_generator(3)  # get a random number generator
+instance = Instance.from_resource("a10")  # pick instance a10
+space = PackingSpace(instance)  # create the packing space
+
+# We test two versions of the Improved Bottom Left Encoding
+encodings = [ImprovedBottomLeftEncoding1(instance),  # the 1st encoding
+             ImprovedBottomLeftEncoding2(instance)]  # the 2nd encoding
+
+# The data that the encoding can convert to a packing is an array with
+# the same length as the total number n_items of objects to pack. Now a
+# packing instance has n_different_items different items, each of which
+# can occur multiple times, i.e., may occur repeatedly.
+# So we include each of n_different_items item IDs exactly as often as
+# it should be repeated. We sometimes include it directly and sometimes
+# in the negated form, which is interpreted as a 90deg rotation by the
+# encoding.
+# Then, we convert the list of item IDs to a numpy array and, finally, shuffle
+# the array. The encoding then inserts the items in the same sequence they
+# appear in the array into bins.
+
+# generate the data for a random packing
+x_data = instance.get_standard_item_sequence()
+for i, e in enumerate(x_data):
+    if random.integers(2) != 0:
+        x_data[i] = -e
+x = np.array(x_data, instance.dtype)  # convert the data to a numpy array
+random.shuffle(x)  # shuffle the data, i.e., the insertion sequence
+
+# The packing is a two-dimensional matrix of items.
+# Each row corresponds to one item. It stores the item ID, the ID of the bin
+# into which the item is to be inserted, as well as the left-x, bottom-y,
+# right-x, and top-y coordinates of the item.
+# We now allocate one packing record for each encoding and apply the
+# encodings.
+ys = []
+for encoding in encodings:
+    y = space.create()  # allocate the packing
+    encoding.decode(x, y)  # perform the encoding
+    space.validate(y)  # check
+    ys.append(y)
+
+
+# We can now plot the packing.  We create the figures in a temp directory.
+# To keep the figures, you would put an existing directory path into `td` by
+# doing `from pycommons.io.path import Path; td = Path("mydir")` and not use
+# the `with` block.
+with temp_dir() as td:  # create temporary directory `td`
+    files = []  # the collection of files
+
+# Plot the packings. The default plotting includes the item ID into each
+# rectangle. If enough space is there, it will also include the index of the
+# item in the bin (starting at 1), and, if then there is still enough space,
+# also the overall index of the item in the encoding (starting at 1).
+    for i, y in enumerate(ys):
+        fig = plot_packing(y, max_bins_per_row=2, max_rows=2)
+
+        # Save the image as svg and png.
+        files.extend(save_figure(
+            fig=fig,  # store fig to a file
+            file_name=f"packing_plot_a10_ibl{i + 1}",  # base name
+            dir_name=td,  # store graphic in temp directory
+            formats=("svg", "png")))  # file types: svg and png
+        del fig  # dispose figure
+
+# OK, we have now generated and saved the plot in a file.
+# We will open it in the web browser if we are not in a make build.
+    if not is_build():
+        for file in files:  # for each file we generated
+            open_new_tab(f"file://{file}")  # open a browser tab
+        sleep(10)  # sleep 10 seconds (enough time for the browser to load)
+# The temp directory is deleted as soon as we leave the `with` block.
+# Hence, all the figures generated above as well as the experimental results
+# now have disappeared.
+
diff --git a/examples/binpacking2d_rls_py.html b/examples/binpacking2d_rls_py.html new file mode 100644 index 00000000..839b5a19 --- /dev/null +++ b/examples/binpacking2d_rls_py.html @@ -0,0 +1,94 @@ +

"""
+Run a small experiment applying RLS to one 2d bin packing instance.
+
+We plug the second variant (`ImprovedBottomLeftEncoding2`) of the
+improved-bottom-left encoding into a simple randomized local search (RLS).
+The encoding processes a signed permutation from the beginning to the end and
+places objects iteratively into bins. For each object, it will always try all
+bins.
+
+We apply the algorithm to instance `a10`. The result of a short run with 1024
+steps of the algorithm is a packing that needs two bins only. You can compare
+this with the example file `binpacking2d_plot.py`, where three bins are needed
+by the same encoding (or four by `ImprovedBottomLeftEncoding1`).
+"""
+from time import sleep
+from webbrowser import open_new_tab
+
+from moptipy.algorithms.so.rls import RLS
+from moptipy.api.execution import Execution
+from moptipy.operators.signed_permutations.op0_shuffle_and_flip import (
+    Op0ShuffleAndFlip,
+)
+from moptipy.operators.signed_permutations.op1_swap_2_or_flip import (
+    Op1Swap2OrFlip,
+)
+from moptipy.spaces.signed_permutations import SignedPermutations
+from moptipy.utils.plot_utils import save_figure
+from pycommons.io.temp import temp_dir
+from pycommons.processes.caller import is_build
+
+from moptipyapps.binpacking2d.encodings.ibl_encoding_2 import (
+    ImprovedBottomLeftEncoding2,
+)
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.objectives.bin_count_and_last_empty import (
+    BinCountAndLastEmpty,
+)
+from moptipyapps.binpacking2d.packing_space import PackingSpace
+from moptipyapps.binpacking2d.plot_packing import plot_packing
+
+# load the problem instance
+instance = Instance.from_resource("a10")  # pick instance a10
+
+search_space = SignedPermutations(
+    instance.get_standard_item_sequence())  # Create the search space.
+solution_space = PackingSpace(instance)  # Create the space of packings.
+y = solution_space.create()  # Here we will put the best solution.
+
+# Build a single execution of a single run of a single algorithm, execute it,
+# and store the best solution discovered in y.
+with Execution()\
+        .set_rand_seed(1)\
+        .set_search_space(search_space)\
+        .set_solution_space(solution_space)\
+        .set_encoding(ImprovedBottomLeftEncoding2(instance))\
+        .set_algorithm(  # This is the algorithm: Randomized Local Search.
+            RLS(Op0ShuffleAndFlip(search_space), Op1Swap2OrFlip()))\
+        .set_objective(BinCountAndLastEmpty(instance))\
+        .set_max_fes(1024)\
+        .execute() as process:
+    process.get_copy_of_best_y(y)
+
+# We can now plot the best packing. We create the figures in a temp directory.
+# To keep the figures, you would put an existing directory path into `td` by
+# doing `from pycommons.io.path import Path; td = Path("mydir")` and not use
+# the `with` block.
+with temp_dir() as td:  # create temporary directory `td`
+    files = []  # the collection of files
+
+# Plot the packing. The default plotting includes the item ID into each
+# rectangle. If enough space is there, it will also include the index of the
+# item in the bin (starting at 1), and, if then there is still enough space,
+# also the overall index of the item in the encoding (starting at 1).
+    fig = plot_packing(y, max_bins_per_row=2, max_rows=2)
+
+    # Save the image as svg and png.
+    files.extend(save_figure(
+        fig=fig,  # store fig to a file
+        file_name="packing_plot_a10",  # base name
+        dir_name=td,  # store graphic in temp directory
+        formats=("svg", "png")))  # file types: svg and png
+    del fig  # dispose figure
+
+# OK, we have now generated and saved the plot in a file.
+# We will open it in the web browser if we are not in a make build.
+    if not is_build():
+        for file in files:  # for each file we generated
+            open_new_tab(f"file://{file}")  # open a browser tab
+        sleep(10)  # sleep 10 seconds (enough time for the browser to load)
+# The temp directory is deleted as soon as we leave the `with` block.
+# Hence, all the figures generated above as well as the experimental results
+# now have disappeared.
+
diff --git a/examples/binpacking2d_timing_py.html b/examples/binpacking2d_timing_py.html new file mode 100644 index 00000000..106245f7 --- /dev/null +++ b/examples/binpacking2d_timing_py.html @@ -0,0 +1,88 @@ +

"""Compare the runtime of encodings on several problem instances."""
+from statistics import mean, median
+from timeit import timeit
+from typing import Any, Callable, cast
+
+import numpy as np
+from moptipy.utils.nputils import rand_generator
+from pycommons.processes.caller import is_build
+from pycommons.types import type_name
+
+from moptipyapps.binpacking2d.encodings.ibl_encoding_1 import (
+    ImprovedBottomLeftEncoding1,
+)
+from moptipyapps.binpacking2d.encodings.ibl_encoding_2 import (
+    ImprovedBottomLeftEncoding2,
+)
+from moptipyapps.binpacking2d.instance import Instance
+from moptipyapps.binpacking2d.packing_space import PackingSpace
+
+# Create the random number generator.
+random = rand_generator(1)
+
+# If it is a make build, use only 1 repetition, otherwise 20.
+REPETITIONS = 1 if is_build() else 20
+
+# The instances to iterate over: All if not make build, 20 otherwise.
+INSTANCES = random.choice(Instance.list_resources(), 20, False) \
+    if is_build() else Instance.list_resources()
+
+
+# We test two versions of the Improved Bottom Left Encoding
+encodings = [ImprovedBottomLeftEncoding1,  # the 1st encoding
+             ImprovedBottomLeftEncoding2]  # the 2nd encoding
+
+# The array to receive the timing measurements
+timings = [[] for _ in encodings]
+
+# Iterate over all instances.
+for inst_name in INSTANCES:
+    instance = Instance.from_resource(inst_name)  # load the instance
+
+# We create 10 points in the search space to be mapped by the encodings.
+    all_x_data_lst: list[np.ndarray] = []
+    for _ in range(10):
+        x_data = instance.get_standard_item_sequence()
+        for i, e in enumerate(x_data):
+            if random.integers(2) != 0:
+                x_data[i] = -e
+        x = np.array(x_data, instance.dtype)
+        random.shuffle(x)
+        all_x_data_lst.append(x)
+    all_x_data: tuple[np.ndarray, ...] = tuple(all_x_data_lst)
+
+    y_space = PackingSpace(instance)
+    y = y_space.create()
+    benchmarks = []
+    for encoding in encodings:
+        def __f(_x=all_x_data, _y=y,
+                _e=cast(Callable[[Any, Any], Any],
+                        encoding(instance).decode)) -> None:
+            for __x in _x:
+                _e(__x, _y)
+        benchmarks.append(__f)
+
+    for f in benchmarks:
+        timeit(f, number=2)
+
+    for i, f in enumerate(benchmarks):
+        t = timeit(f, number=10)
+        timings[i].append(t)
+
+
+def get_short_name(the_type) -> str:
+    """Get the short name of the type."""
+    s = type_name(the_type)
+    last_dot = s.rfind(".")
+    if last_dot > 0:
+        return s[(last_dot + 1):]
+    return s
+
+
+# Print the times measured for the different encodings.
+for i, e in enumerate(encodings):
+    print(f"{get_short_name(e)}: "
+          f"{min(timings[i]):.4f}/{mean(timings[i]):.4f}/"
+          f"{median(timings[i]):.4f}/{max(timings[i]):.4f}")
+
diff --git a/examples/order1_from_dat_py.html b/examples/order1_from_dat_py.html new file mode 100644 index 00000000..4515e7cc --- /dev/null +++ b/examples/order1_from_dat_py.html @@ -0,0 +1,255 @@ +

"""
+Find a reasonable one-dimensional order for permutations.
+
+Solve the one-dimensional ordering problem for permutations with fitness
+stored in the `dat` file format as used by Thomson et al. This program
+loads permutations from one or multiple files of this type and then tries
+to arrange them on a horizontal axis such that their neighborhood relation
+on the one-dimensional axis represents their neighborhood based one the
+swap distance metric in the permutation space. It does this re-arrangement
+by applying a randomized local search (RLS) to find the right permutation
+of these permutations to be mapped to one-dimensional coordinates.
+
+You can call this program as follows:
+
+`python3 order1_from_dat.py sourceData outputFile \
+[fitnessCap] [maxFEs] [fileNameRegEx]`
+
+where
+- `sourceData` is the path to a file or folder with the original data.
+  If it is a file, the file will be loaded if it matches `fileNameRegEx`.
+  If it is a folder, all files in this folder and all sub-folders will
+  recursively be parsed for input data.
+- `outputFile` is the path to a file where to store the result.
+- `fitnessCap` is an upper limit for the fitness values stored in the files,
+  all points with higher fitness are ignored. (default: So high that it does
+  not matter.)
+- `maxFEs` are the maximum number of optimization steps until done. By
+  default, these are `10000`.
+- `fileNameRegEx` is a regular expression. Only file names matching this
+  regular expression are parsed. By default, it matches all files. But you
+  could provide `tai27e01.*`, e.g., to only match files starting with
+  `tai27e01` (make sure to write `"tai27e01.*"` on the command line to prevent
+  the dot-start from being expanded by the shell),
+
+The input format of this program are `dat` files of the format
+```
+EVALS    GENOTYPE    FITNESS
+1    [22, 7, 6, 26, 27, 19, 3, 1, ... 5, 21, 8, 17, 2, 16, 9, 23]    87018
+13    [20, 7, 6, 26, 18, 19, 9, 1, ... 25, 13, 23, 16, 15, 24]    85456
+20    [20, 7, 18, 26, 6, 16, 9, 1,  ...  21, 13, 12, 19, 15, 17]    84152
+29    [20, 11, 14, 25, 5, 16, 15, 1,  ...  21, 13, 12, 9, 19, 17]    83180
+32    [20, 10, 14, 25, 5, 12, 15, 1,  ... 17, 13, 16, 9, 19, 21]    82846
+34    [20, 15, 14, 25, 5, 12, 10, 1,  ...  6, 17, 13, 16, 9, 19, 21]    78204
+```
+
+`fitnessCap` reflects an upper limit to the `FITNESS` column. Setting it to
+`80000` would, for example, lead to ignoring all but the last line in the
+above example.
+"""
+
+import argparse
+from os import listdir
+from os.path import basename, dirname, isdir, isfile, join
+from re import Pattern
+from re import compile as re_compile
+from typing import Any, Callable, Final
+
+import numpy as np
+from matplotlib.axes import Axes
+from matplotlib.figure import Figure
+from moptipy.algorithms.so.rls import RLS
+from moptipy.api.execution import Execution
+from moptipy.operators.permutations.op0_shuffle import Op0Shuffle
+from moptipy.operators.permutations.op1_swap2 import Op1Swap2
+from moptipy.utils.nputils import rand_seeds_from_str
+from moptipy.utils.plot_defaults import distinct_colors, distinct_markers
+from moptipy.utils.plot_utils import create_figure, get_axes, save_figure
+from pycommons.io.console import logger
+from pycommons.io.path import Path, directory_path
+from pycommons.types import check_to_int_range
+
+from moptipyapps.order1d.distances import swap_distance
+from moptipyapps.order1d.instance import Instance
+from moptipyapps.order1d.space import OrderingSpace
+from moptipyapps.qap.objective import QAPObjective
+from moptipyapps.shared import moptipyapps_argparser
+
+#: the impact of rank differences
+POWER: Final[float] = 2.0
+#: ignore everything that is farther away than the 64th nearest neighbor
+HORIZON: Final[int] = 1024
+#: the fitness tag
+TAG_FITNESS: Final[str] = "fitness"
+#: the evaluation tag
+TAG_EVAL: Final[str] = "evaluations"
+#: the permutation
+TAG_PERM: Final[str] = "permutation"
+#: the file
+TAG_FILE: Final[str] = "file"
+
+
+def parse_data(path: str, collector: Callable[[
+        tuple[str, int, int, np.ndarray]], Any],
+        fitness_limit: int, pattern: Pattern) -> None:
+    """
+    Parse a dat file.
+
+    :param path: the path
+    :param collector: the collector function to invoke when loading data
+    :param fitness_limit: the minimum acceptable fitness
+    :param pattern: the file name pattern
+    """
+    the_path: Final[Path] = Path(path)
+    if isdir(the_path):  # recursively parse directories
+        logger(f"recursing into directory '{the_path}'.")
+        for subpath in listdir(the_path):
+            parse_data(join(the_path, subpath), collector, fitness_limit,
+                       pattern)
+        return
+
+    if not isfile(the_path):
+        return  # if it is not a file, we quit
+    the_name: Final[str] = basename(the_path)
+    if not pattern.match(the_name):
+        return  # file does not match
+
+    # parse the file
+    for oline in the_path.open_for_read():
+        line = oline.strip()
+        if len(line) <= 0:
+            continue
+        bracket_open: int = line.find("[")
+        if bracket_open <= 0:
+            continue
+        bracket_close: int = line.find("]", bracket_open + 1)
+        if bracket_close <= bracket_open:
+            continue
+        f: int = check_to_int_range(line[bracket_close + 1:],
+                                    TAG_FITNESS, 0, 1_000_000_000_000)
+        if f > fitness_limit:
+            continue
+        evals: int = check_to_int_range(line[:bracket_open].strip(),
+                                        TAG_EVAL, 1, 1_000_000_000_000_000)
+        perm: list[int] = [
+            check_to_int_range(s, TAG_PERM, 1, 1_000_000_000) - 1
+            for s in line[bracket_open + 1:bracket_close].split(",")]
+        collector((the_name, evals, f, np.array(perm)))
+
+
+def get_tags(data: tuple[str, int, int, np.ndarray]) \
+        -> tuple[str, str, str, str]:
+    """
+    Get the tags to store along with the data.
+
+    :param data: the data
+    :return: the tags
+    """
+    return (data[0], str(data[1]), str(data[2]),
+            f"\"[{','.join(str(a + 1) for a in data[3])}]\"")
+
+
+def get_distance(a: tuple[str, int, int, np.ndarray],
+                 b: tuple[str, int, int, np.ndarray]) -> int:
+    """
+    Get the distance between two data elements.
+
+    The distance here is the swap distance.
+
+    :param a: the first element
+    :param b: the second element
+    :return: the swap distance
+    """
+    return swap_distance(a[3], b[3])
+
+
+def run(source: str, dest: str, max_fes: int, fitness_limit: int,
+        file_name_regex: str) -> None:
+    """
+    Run the RLS algorithm to optimize a horizontal layout permutation.
+
+    :param source: the source file or directory
+    :param dest: the destination file
+    :param max_fes: the maximum FEs
+    :param fitness_limit: the minimum acceptable fitness
+    :param file_name_regex: the file name regular expression
+    """
+    logger(f"invoked program with source='{source}', dest='{dest}', "
+           f"max_fes={max_fes}, fitness_limit={fitness_limit}, and "
+           f"file_name_regex='{file_name_regex}'.")
+    # first, we load all the data to construct a distance rank matrix
+    pattern: Final[Pattern] = re_compile(file_name_regex)
+    logger(f"now loading data from '{source}' matching to '{pattern}'.")
+
+    data: list[tuple[str, int, int, np.ndarray]] = []
+    parse_data(source, data.append, fitness_limit, pattern)
+    logger(f"finished loading {len(data)} rows of data, "
+           "now constructing distance rank matrix.")
+    instance: Final[Instance] = Instance.from_sequence_and_distance(
+        data, get_distance, POWER, HORIZON,
+        (TAG_FILE, TAG_EVAL, TAG_FITNESS, TAG_PERM), get_tags)
+    del data  # free the now useless data
+
+    # run the algorithm
+    dest_file: Final[Path] = Path(f"{dest}.txt")
+    logger(f"finished constructing matrix with {instance.n} rows, "
+           "now doing optimization for "
+           f"{max_fes} FEs and, afterwards, writing result to '{dest_file}'.")
+    space: Final[OrderingSpace] = OrderingSpace(instance)
+    result: Final[np.ndarray] = space.create()
+    with (Execution().set_solution_space(space)
+          .set_objective(QAPObjective(instance))
+          .set_algorithm(RLS(Op0Shuffle(space), Op1Swap2()))
+          .set_max_fes(max_fes)
+          .set_log_improvements(True)
+          .set_log_file(dest_file)
+          .set_rand_seed(rand_seeds_from_str(source, 1)[0])
+            .execute()) as proc:
+        proc.get_copy_of_best_y(result)
+    logger("finished optimizing, got the solution "
+           f"{','.join(map(str, result))!r}.")
+
+    files: Final[list[str]] = sorted({f[0][0] for f in instance.tags})
+    logger(f"now we begin plotting the data from {len(files)} files.")
+    dest_dir: Final[Path] = directory_path(dirname(dest_file))
+    figure: Final[Figure] = create_figure()
+    colors: Final[tuple[tuple[float, float, float], ...]] \
+        = distinct_colors(len(files))
+    markers: Final[tuple[str, ...]] = distinct_markers(4)
+    axes: Final[Axes] = get_axes(figure)
+    for i, file in enumerate(files):
+        plot_data = np.array(
+            sorted([(result[idx], int(tags[2])) for tags, idx in
+                    instance.tags if tags[0] == file],
+                   key=lambda a: (a[1], a[0])), dtype=int)
+        axes.plot(plot_data[:, 0], plot_data[:, 1], color=colors[i],
+                  marker=markers[i % len(markers)])
+    axes.set_xticks([])
+    save_figure(figure, basename(dest), dest_dir, "pdf")
+    logger("finished plotting.")
+
+
+# Perform the optimization
+if __name__ == "__main__":
+    parser: Final[argparse.ArgumentParser] = moptipyapps_argparser(
+        __file__, "One-Dimensional Ordering of Permutations",
+        "Run the one-dimensional order of permutations experiment.")
+    parser.add_argument(
+        "source", help="the directory or file with the input data",
+        type=Path, nargs="?", default="./")
+    parser.add_argument(
+        "dest", help="the file to write the output to",
+        type=Path, nargs="?", default="./result.txt")
+    parser.add_argument("fitnessLimit", help="the minimum acceptable fitness",
+                        type=int, nargs="?", default=1_000_000_000)
+    parser.add_argument("maxFEs", help="the maximum FEs to perform",
+                        type=int, nargs="?", default=10_000)
+    parser.add_argument(
+        "fileNameRegEx",
+        help="a regular expression that file names must match",
+        type=str, nargs="?", default=".*")
+    args: Final[argparse.Namespace] = parser.parse_args()
+    run(args.source, args.dest, args.maxFEs, args.fitnessLimit,
+        args.fileNameRegEx)
+
diff --git a/examples/qap_example_experiment_rls_rs_py.html b/examples/qap_example_experiment_rls_rs_py.html new file mode 100644 index 00000000..d8fe99dd --- /dev/null +++ b/examples/qap_example_experiment_rls_rs_py.html @@ -0,0 +1,92 @@ +

"""An example experiment for the Quadratic Assignment Problem."""
+
+import argparse
+from typing import Callable, Final, Iterable
+
+from moptipy.algorithms.random_sampling import RandomSampling
+from moptipy.algorithms.so.rls import RLS
+from moptipy.api.execution import Execution
+from moptipy.api.experiment import run_experiment
+from moptipy.operators.permutations.op0_shuffle import Op0Shuffle
+from moptipy.operators.permutations.op1_swap2 import Op1Swap2
+from moptipy.spaces.permutations import Permutations
+from pycommons.io.path import Path
+
+from moptipyapps.qap.instance import Instance
+from moptipyapps.qap.objective import QAPObjective
+from moptipyapps.shared import moptipyapps_argparser
+
+
+def make_instances() -> Iterable[Callable[[], Instance]]:
+    """
+    Create the instances to be used in the QAP experiment.
+
+    :return: the instances to be used in the QAP experiment.
+    """
+    return map(lambda i: lambda _i=i: Instance.from_resource(i),
+               Instance.list_resources())
+
+
+def base_setup(instance: Instance) -> tuple[Permutations, Execution]:
+    """
+    Create the basic setup.
+
+    :param instance: the instance to use
+    :return: the basic execution
+    """
+    perms: Final[Permutations] = Permutations.standard(instance.n)
+    return (perms, Execution().set_max_fes(32768).set_log_improvements(
+        True).set_objective(QAPObjective(instance)).set_solution_space(perms))
+
+
+def rls(instance: Instance) -> Execution:
+    """
+    Create the RLS execution.
+
+    :param instance: the problem instance
+    :return: the setup
+    """
+    space, exe = base_setup(instance)
+    return exe.set_algorithm(RLS(Op0Shuffle(space), Op1Swap2()))
+
+
+def rs(instance: Instance) -> Execution:
+    """
+    Create the random sampling execution.
+
+    :param instance: the problem instance
+    :return: the setup
+    """
+    space, exe = base_setup(instance)
+    return exe.set_algorithm(RandomSampling(Op0Shuffle(space)))
+
+
+def run(base_dir: str, n_runs: int = 3) -> None:
+    """
+    Run the experiment.
+
+    :param base_dir: the base directory
+    :param n_runs: the number of runs
+    """
+    use_dir: Final[Path] = Path(base_dir)
+    use_dir.ensure_dir_exists()
+
+    run_experiment(
+        base_dir=use_dir,
+        instances=make_instances(),
+        setups=[rls, rs],
+        n_runs=n_runs)
+
+
+# Run the experiment from the command line
+if __name__ == "__main__":
+    parser: Final[argparse.ArgumentParser] = moptipyapps_argparser(
+        __file__, "Quadratic Assignment Problem (QAP)",
+        "Run the QAP experiment.")
+    parser.add_argument(
+        "dest", help="the directory to store the experimental results under",
+        type=Path, nargs="?", default="./results/")
+    args: Final[argparse.Namespace] = parser.parse_args()
+    run(args.dest)
+
diff --git a/examples/tsp_rls_py.html b/examples/tsp_rls_py.html new file mode 100644 index 00000000..828dfddb --- /dev/null +++ b/examples/tsp_rls_py.html @@ -0,0 +1,35 @@ +

"""Run a small experiment applying RLS to one TSP instance."""
+
+from moptipy.algorithms.so.rls import RLS
+from moptipy.api.execution import Execution
+from moptipy.operators.permutations.op0_shuffle import Op0Shuffle
+from moptipy.operators.permutations.op1_swapn import Op1SwapN
+from moptipy.spaces.permutations import Permutations
+
+from moptipyapps.tsp.instance import Instance
+from moptipyapps.tsp.tour_length import TourLength
+
+# load the problem instance and define search space
+instance = Instance.from_resource("gr17")  # pick instance gr17
+space = Permutations.standard(instance.n_cities)
+
+y = space.create()  # will later be used to hold the best solution found
+
+# Build a single execution of a single run of a single algorithm, execute it,
+# and store the best solution discovered in y and its length in `length`.
+with Execution()\
+        .set_rand_seed(1)\
+        .set_solution_space(space)\
+        .set_algorithm(  # This is the algorithm: Randomized Local Search.
+            RLS(Op0Shuffle(space), Op1SwapN()))\
+        .set_objective(TourLength(instance))\
+        .set_max_fes(2048)\
+        .execute() as process:
+    process.get_copy_of_best_y(y)
+    length = process.get_best_f()
+
+print(f"tour length: {length}")
+print(f"optimal length: {instance.tour_length_lower_bound}")
+print(f"tour: {', '.join(str(i) for i in y)}")
+
diff --git a/examples/tsp_special_algorithms_py.html b/examples/tsp_special_algorithms_py.html new file mode 100644 index 00000000..d61a07ba --- /dev/null +++ b/examples/tsp_special_algorithms_py.html @@ -0,0 +1,30 @@ +

"""Run small experiments applying Algorithms Specialized to the TSP."""
+
+from moptipy.api.execution import Execution
+from moptipy.spaces.permutations import Permutations
+
+from moptipyapps.tsp.ea1p1_revn import TSPEA1p1revn
+from moptipyapps.tsp.fea1p1_revn import TSPFEA1p1revn
+from moptipyapps.tsp.instance import Instance
+from moptipyapps.tsp.tour_length import TourLength
+
+# load the problem instance and define search space
+instance = Instance.from_resource("burma14")  # pick instance burma14
+space = Permutations.standard(instance.n_cities)
+
+# the specialized algorithms that we will try out
+algorithms = [TSPEA1p1revn, TSPFEA1p1revn]
+
+# Apply the algorithms to the smallest instance, burma14.
+for constructor in algorithms:
+    algorithm = constructor(instance)
+    with Execution()\
+            .set_rand_seed(1)\
+            .set_solution_space(space)\
+            .set_algorithm(algorithm)\
+            .set_objective(TourLength(instance))\
+            .set_max_fes(1000)\
+            .execute() as process:
+        print(f"{algorithm}: {process.get_best_f()}")
+
diff --git a/examples/ttp_example_experiment_mo_py.html b/examples/ttp_example_experiment_mo_py.html new file mode 100644 index 00000000..7761c941 --- /dev/null +++ b/examples/ttp_example_experiment_mo_py.html @@ -0,0 +1,116 @@ +

"""
+An example experiment for the multi-objective Traveling Tournament Problem.
+
+In this experiment, we apply multi-objective methods to the TTP. The idea
+is to treat the feasibility constraint :mod:`~moptipyapps.ttp.errors` as an
+objective function and optimize it together with the actual
+:mod:`~moptipyapps.ttp.plan_length` objective measuring the travel time.
+We can do this either in a multi-objective fashion or in a single objective
+fashion where we prioritize the number of errors over the length of the game
+plans.
+"""
+
+import argparse
+from typing import Callable, Final, Iterable
+
+from moptipy.algorithms.mo.nsga2 import NSGA2
+from moptipy.algorithms.so.rls import RLS
+from moptipy.api.experiment import run_experiment
+from moptipy.api.mo_execution import MOExecution
+from moptipy.mo.problem.weighted_sum import Prioritize
+from moptipy.operators.permutations.op0_shuffle import Op0Shuffle
+from moptipy.operators.permutations.op1_swap2 import Op1Swap2
+from moptipy.operators.permutations.op2_gap import (
+    Op2GeneralizedAlternatingPosition,
+)
+from moptipy.spaces.permutations import Permutations
+from pycommons.io.path import Path
+
+from moptipyapps.shared import moptipyapps_argparser
+from moptipyapps.ttp.errors import Errors
+from moptipyapps.ttp.game_encoding import GameEncoding
+from moptipyapps.ttp.game_plan_space import GamePlanSpace
+from moptipyapps.ttp.instance import Instance
+from moptipyapps.ttp.plan_length import GamePlanLength
+
+
+def make_instances() -> Iterable[Callable[[], Instance]]:
+    """
+    Create the instances to be used in the TTP experiment.
+
+    :return: the instances to be used in the TTP experiment.
+    """
+    return map(lambda i: lambda _i=i: Instance.from_resource(i),
+               Instance.list_resources())
+
+
+def base_setup(instance: Instance) -> tuple[Permutations, MOExecution]:
+    """
+    Create the basic setup.
+
+    :param instance: the instance to use
+    :return: the basic execution
+    """
+    ge: Final[GameEncoding] = GameEncoding(instance)
+    perms: Final[Permutations] = ge.search_space()
+    return (perms, MOExecution().set_max_fes(512 * 1024).
+            set_log_improvements(True).set_objective(
+        Prioritize((Errors(instance), GamePlanLength(instance))))
+        .set_search_space(perms).set_solution_space(
+        GamePlanSpace(instance)).set_encoding(
+        GameEncoding(instance)))
+
+
+def rls(instance: Instance) -> MOExecution:
+    """
+    Create the priority-based RLS execution.
+
+    :param instance: the problem instance
+    :return: the setup
+    """
+    space, exe = base_setup(instance)
+    return exe.set_algorithm(RLS(Op0Shuffle(space), Op1Swap2()))
+
+
+def mo_nsga2(instance: Instance) -> MOExecution:
+    """
+    Create the multi-objective NSGA-2 execution.
+
+    :param instance: the problem instance
+    :return: the setup
+    """
+    space, exe = base_setup(instance)
+    return exe.set_algorithm(NSGA2(Op0Shuffle(space), Op1Swap2(),
+                                   Op2GeneralizedAlternatingPosition(space),
+                                   16, 1 / 16))
+
+
+def run(base_dir: str, n_runs: int = 3) -> None:
+    """
+    Run the experiment.
+
+    :param base_dir: the base directory
+    :param n_runs: the number of runs
+    """
+    use_dir: Final[Path] = Path(base_dir)
+    use_dir.ensure_dir_exists()
+
+    run_experiment(
+        base_dir=use_dir,
+        instances=make_instances(),
+        setups=[rls, mo_nsga2],
+        n_runs=n_runs)
+
+
+# Run the experiment from the command line
+if __name__ == "__main__":
+    parser: Final[argparse.ArgumentParser] = moptipyapps_argparser(
+        __file__, "Traveling Tournament Problem (TTP)",
+        "Run the Multi-Objective TTP experiment.")
+    parser.add_argument(
+        "dest", help="the directory to store the experimental results under",
+        type=Path, nargs="?", default="./results/")
+    args: Final[argparse.Namespace] = parser.parse_args()
+    run(args.dest)
+
diff --git a/examples/ttp_example_experiment_rls_rs_py.html b/examples/ttp_example_experiment_rls_rs_py.html new file mode 100644 index 00000000..36a01b6a --- /dev/null +++ b/examples/ttp_example_experiment_rls_rs_py.html @@ -0,0 +1,134 @@ +

"""
+An example experiment for the Traveling Tournament Problem.
+
+In this experiment, we apply both a random sampling algorithm (RS) and a
+randomized local search (RLS) to the Traveling Tournament Problem (TTP).
+
+Our goal is only to find feasible game plans, not feasible + short ones.
+So we only use the "errors" objective function. Since we only want to find
+feasible plans, we also do not need to consider the different distance
+matrices that are available. Thus, we only use the 19 `circ*` instances
+with numbers of teams ranging from 4 to 40. (See `make_instances`.)
+
+We apply both algorithms on a permutation-based search spaces using the
+very simple game encoding procedure (which may produce infeasible plans)
+for 32768 FEs and log all improving moves under the "errors" objective
+function. (See `base_setup`.)
+
+Both algorithms use random permutation shuffling as their nullary operator.
+Random sampling only applies this operator again and again and only creates
+random solutions. It thus is rarely able to find a feasible solution and
+does so only on the smallest problem instances. This random sampling algorithm
+is set up in function `rs`.
+
+Randomized local search - set up in function `rls` - additionally uses a unary
+operator that receives an existing permutation as input and produces a
+slightly modified copy as output. This enables it to try to improve the best
+solution that it has encountered so far. It does so by applying the unary
+operator to it, obtaining a new slightly modified copy of that best solution,
+and keeping this copy as new best-so-far solution if it is *not worse*, i.e.,
+better or equally good. As unary operator, it applies a simple swap-2 method
+that exchanges two randomly chosen (different) elements in the permutation.
+
+Three runs are executed for every algorithm-instance combination (see function
+`run`).
+"""
+
+import argparse
+from typing import Callable, Final, Iterable, cast
+
+from moptipy.algorithms.random_sampling import RandomSampling
+from moptipy.algorithms.so.rls import RLS
+from moptipy.api.execution import Execution
+from moptipy.api.experiment import run_experiment
+from moptipy.operators.permutations.op0_shuffle import Op0Shuffle
+from moptipy.operators.permutations.op1_swap2 import Op1Swap2
+from moptipy.spaces.permutations import Permutations
+from pycommons.io.path import Path
+
+from moptipyapps.shared import moptipyapps_argparser
+from moptipyapps.ttp.errors import Errors
+from moptipyapps.ttp.game_encoding import GameEncoding
+from moptipyapps.ttp.game_plan_space import GamePlanSpace
+from moptipyapps.ttp.instance import Instance
+
+
+def make_instances() -> Iterable[Callable[[], Instance]]:
+    """
+    Create the instances to be used in the TTP experiment.
+
+    Here, we simply load all the `circ*` instances. Here, all cities
+    are located on a circle.
+
+    :return: the instances to be used in the TTP experiment.
+    """
+    return (cast(Callable[[], Instance], lambda j=i: Instance.from_resource(
+        f"circ{j}")) for i in range(4, 42, 2))
+
+
+def base_setup(instance: Instance) -> tuple[Permutations, Execution]:
+    """
+    Create the basic setup.
+
+    :param instance: the instance to use
+    :return: the basic execution
+    """
+    ge: Final[GameEncoding] = GameEncoding(instance)
+    perms: Final[Permutations] = ge.search_space()
+    return (perms, Execution().set_max_fes(32768).set_log_improvements(
+        True).set_objective(Errors(instance)).set_search_space(perms)
+        .set_solution_space(GamePlanSpace(instance)).set_encoding(
+        GameEncoding(instance)))
+
+
+def rls(instance: Instance) -> Execution:
+    """
+    Create the RLS execution.
+
+    :param instance: the problem instance
+    :return: the setup
+    """
+    space, exe = base_setup(instance)
+    return exe.set_algorithm(RLS(Op0Shuffle(space), Op1Swap2()))
+
+
+def rs(instance: Instance) -> Execution:
+    """
+    Create the random sampling execution.
+
+    :param instance: the problem instance
+    :return: the setup
+    """
+    space, exe = base_setup(instance)
+    return exe.set_algorithm(RandomSampling(Op0Shuffle(space)))
+
+
+def run(base_dir: str, n_runs: int = 3) -> None:
+    """
+    Run the experiment.
+
+    :param base_dir: the base directory
+    :param n_runs: the number of runs
+    """
+    use_dir: Final[Path] = Path(base_dir)
+    use_dir.ensure_dir_exists()
+
+    run_experiment(
+        base_dir=use_dir,
+        instances=make_instances(),
+        setups=[rls, rs],
+        n_runs=n_runs)
+
+
+# Run the experiment from the command line
+if __name__ == "__main__":
+    parser: Final[argparse.ArgumentParser] = moptipyapps_argparser(
+        __file__, "Traveling Tournament Problem (TTP)",
+        "Run the TTP experiment.")
+    parser.add_argument(
+        "dest", help="the directory to store the experimental results under",
+        type=Path, nargs="?", default="./results/")
+    args: Final[argparse.Namespace] = parser.parse_args()
+    run(args.dest)
+
diff --git a/genindex.html b/genindex.html new file mode 100644 index 00000000..d592e622 --- /dev/null +++ b/genindex.html @@ -0,0 +1 @@ +Index — moptipyapps 0.8.62 documentation

Index

A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W

A

B

C

D

E

F

G

H

I

J

K

L

M

N

O

P

Q

R

S

T

U

V

W

diff --git a/index.html b/index.html new file mode 100644 index 00000000..25b4e3be --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ +moptipyapps: Applications of Metaheuristic Optimization in Python — moptipyapps 0.8.62 documentation

moptipyapps: Applications of Metaheuristic Optimization in Python

build pypi version pypi downloads coverage report

1. Introduction

moptipy is a library with implementations of metaheuristic optimization methods in Python 3.12 that also offers an environment for replicable experiments (flyer). moptipyapps is a collection of applications and experiments based on moptipy.

2. Installation

In order to use this package and to, e.g., run the example codes, you need to first install it using pip or some other tool that can install packages from PyPi. You can install the newest version of this library from PyPi using pip by doing

pip install moptipyapps
+

This will install the latest official release of our package as well as all dependencies. If you want to install the latest source code version from GitHub (which may not yet be officially released), you can do

pip install git+https://github.com/thomasWeise/moptipyapps.git
+

If you want to install the latest source code version from GitHub (which may not yet be officially released) and you have set up a private/public key for GitHub, you can also do:

git clone ssh://git@github.com/thomasWeise/moptipyapps
+pip install moptipyapps
+

This may sometimes work better if you are having trouble reaching GitHub via https or http.

You can also clone the repository and then run a make build, which will automatically install all dependencies, run all the tests, and then install the package on your system, too. This will work only on Linux, though. It also installs the dependencies for building, which include, e.g., those for unit testing and static analysis. If this build completes successful, you can be sure that moptipyapps will work properly on your machine.

All dependencies for using and running moptipyapps are listed at here. The additional dependencies for a full make build, including unit tests, static analysis, and the generation of documentation are listed here.

3. Applications

Here we list the applications of moptipy. Step by step, we will add more and more interesting optimization problems. For each problem, we provide means to load the problem instances and a basic infrastructure to construct optimization algorithms for solving them.

3.1. Two-Dimensional Bin Packing Problem

In the package moptipyapps.binpacking2d, we provide tools for experimenting and playing around with the two-dimensional bin packing problem. Bin packing is a classical domain from Operations Research. The goal is to pack objects into containers, the so-called bins. We address two-dimensional rectangular bin packing. We provide the bin packing instances from 2DPackLib as resources together with this package as well as the four non-trivial “Almost Squares in Almost Squares” instances. Each such instances defines a set of n_different_items objects Oi with i from 1..n_different_objects. Each object Oi is a rectangle with a given width and height. The object occur is a given multiplicity repetitions(O_i), i.e., either only once or multiple times. The bins are rectangles with a given width and height too. The goal of tackling such an instance is to package all the objects into as few as possible bins. The objects therefore may be rotated by 90 degrees.

We address this problem by representing a packing as a signed permutation with repetitions of the numbers 1..n_different_objects, where the number i occurs repetitions(O_i) times. If an object is to be placed in a rotated way, this is denoted by using -i instead of i. Such permutations are processed from beginning to end, placing the objects into bins as they come according to some heuristic encoding. We provide two variants of the Improved Bottom Left encoding. The first one closes bins as soon as one object cannot be placed into them. The second one tries to put each object in the earliest possible bin. While the former one is faster, the latter one leads to better packings.

We can then apply a black-box metaheuristic to search in the space of these signed permutations with repetitions. The objective function would be some measure consistent with the goal of minimizing the number of bins used.

Examples:

Important work on this code has been contributed by Mr. Rui ZHAO (赵睿), zr1329142665@163.com, a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥学院) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授), who then refined the implementations.

3.2. The Traveling Salesperson Problem (TSP)

In the package moptipyapps.tsp, we provide tools to run experiments and play around with the Traveling Salesperson Problem (TSP) . A TSP instance is defined as a fully-connected graph with n_cities nodes. Each edge in the graph has a weight, which identifies the distance between the nodes. The goal is to find the shortest tour that visits every single node in the graph exactly once and then returns back to its starting node. Then nodes are usually called cities. A tour can be represented in path representation, which means that it is stored as a permutation of the numbers 0 to n_cities-1. The number at index k identifies that k-th city to visit. So the first number in the permutation identifies the first city, the second number the second city, and so on. The length of the tour can be computed by summing up the distances from the k-th city to the k+1-st city, for k in 0..n_cities-2 and then adding the distance from the last city to the first city.

We use the TSP instances from TSPLib, the maybe most important benchmark set for the TSP. 110 of these instances are included as resources in this package.

Examples:

Important work on this code has been contributed by Mr. Tianyu LIANG (梁天宇), liangty@stu.hfuu.edu.cn a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥学院) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

The Traveling Tournament Problem (TTP) is related to the TTP.

3.3. Dynamic Controller Synthesis

Another interesting example for optimization is the synthesis of active controllers for dynamic systems. Dynamic systems have a state that changes over time based on some laws. These laws may be expressed as ordinary differential equations, for example. The classical Stuart-Landau system, for instance, represents an object whose coordinates on a two-dimensional plane change as follows:

sigma = 0.1 - x² - y²
+dx/dt = sigma * x - y
+dy/dt = sigma * y + x
+

Regardless on which (x, y) the object initially starts, it tends to move to a circular rotation path centered around the origin with radius sqrt(0.1). Now we try to create a controller ctrl for such a system that moves the object from this periodic circular path into a fixed and stable location. The controller ctrl receives the current state, i.e., the object location, as input and can influence the system as follows:

sigma = 0.1 - x² - y²
+c = ctrl(x, y)
+dx/dt = sigma * x - y
+dy/dt = sigma * y + x + c
+

What we try to find is the controller which can bring move object to the origin (0, 0) as quickly as possible while expending the least amount of force, i.e., having the smallest aggregated c values over time.

3.4. The Traveling Tournament Problem (TTP)

In the package moptipyapps.ttp, we provide a set of classes and tools to explore the Traveling Tournament Problem (TTP). In a TTP, we have an even number of n teams. Each team plays a tournament against every other team. If the tournament is a single round-robin tournament, each team plays exactly once against every other team. In the more common double round-robin tournament, each team plays twice against every other team — once at home and once at the place of the other team. A tournament takes (n - 1) * rounds days in total, where rounds = 2 for double round-robin. Now additionally to the basic constraints dictated by logic (if team A plays at home against team B on day d, then team B has an “away” game against team A on that day d and so on), there are also additional constraints. For instance, no team should play a continuous streak of home (or away) games longer than m days, where m usually is m = 3. Also, if teams A and B play against each other, then there must be at least p games in between before they play each other again, usually with p = 1.

Now the first hurdle is to find a game plan that has n / 2 games on each day (since there are n teams and each plays against one other team) that satisfies the above constraints. The second problem is that this is not all: For each TTP, a distance matrix is defined, very much like for the TSP. The goal is to find a feasible game schedule where the overall travel distances are minimal.

Examples:

3.5. The Quadratic Assignment Problem (QAP)

In the package moptipyapps.qap, we implement some utilities to play with the Quadratic Assignment Problem (QAP). The QAP is one of the very classical problems from Operations Research. Imagine you are planning the layout for a factory. The goal is to assign n facilities (say, machines or workshops) to n locations. Now between the facilities, there exists a flow of goods. The output of one facility may be the input of another one and vice versa. The amount of stuff to be transported is likely to be different between different facilities. Between some facilities, a lot of things may need to be transport. Between others, there could be no exchange of material or only very little. The available locations also have different distances among each other. Some locations are closer, some are farther from each other. The goal is to find an assignment of facilities to locations such that the overall sum of the product of flow and distance for each facility pair gets minimized. To this end, solutions can be represented as permutations of facilities determining the order in which they are placed on the locations 1 to n.

Examples:

3.6. One-Dimensional Ordering

In the package moptipyapps.order1d, we implement what I would like to call the “one-dimensional ordering problem”. Imagine that you have n objects and you only know the distances between them. You want to arrange these objects on one axis, e.g., along the horizontal (x) axis, i.e., in a one-dimensional space. Now what you care about is to reflect the neighborhood structure among the objects (as defined by the distance matrix that you got) to the one-dimensional space. So the closest neighbor of a given object based on the distance matrix should also be the closest neighbor on the one-dimensional axis.

The goal of solving this problem is thus to arrange the n objects on a 1-dimensional (e.g., horizontal) axis given a distance matrix describing (maybe derived from their location in a potentially high-dimensional or unstructured space). The objects should be arranged in such a way that, for each object,

  • the nearest neighbors on the 1-dimensional axis are also the nearest neighbors in the original space (according to the distance matrix provided),

  • the second nearest neighbors on the 1-dimensional axis are also the second nearest neighbors in the original space (according to the distance matrix provided),

  • the third nearest neighbors on the 1-dimensional axis are also the third nearest neighbors in the original space (according to the distance matrix provided),

  • and so on; with (quadratically) decreasing weights of neighbor distance ranks.

The original distances be limited to integers for the sake of simplicity, but we may use floats as well if we want to. Either way, we do not care about the actual precise distances (e.g., something like “0.001”) between the objects on either the one-dimensional nor the original space. Only about the distance ranks, i.e., about “2nd nearest neighbor,” but not “0.012 distance units away.” The solutions of this problem are thus permutations (orders) of the objects. Of course, if we really want to plot the objects, such a permutation can easily be translated to x-coordinates, say, by dividing the index of an object by the number of objects, which nets values in [0,1]. But basically, we reduce the task to finding permutations of objects that reflect the neighbor structure of the original space as closely as possible.

If such a problem is solved correctly, then the arrangement on the one-dimensional axis should properly reflect the arrangement of the objects in the original space. Of course, solving this problem exactly may not actually be possible, since an object on a one-dimensional axis may either have exactly two i-nearest-neighbors (if it is at least i slots away from either end of the permutation) or exactly 1 such neighbor, if it is closer that i units. The object directly at the start of the permutation has only 1 nearest neighbor (the object that comes next). That next object, however, has two, namely the first object and the third object. In the original space where the objects come from, however, there may be any number of “nearest neighbors.” Imagine a two-dimensional space where one object sits at the center of a circle of other objects. Then all other objects are its nearest neighbors, whereas an object on the circle either has exactly two nearest neighbors or, maybe, in the odd situation that the radius equals a multiple of the distance to the neighbors on the circle, three. Such a structure cannot be represented exactly in one dimension.

But that’s OK. Because we mainly do this for visualization purposes anyway.

Examples:

4. Unit Tests and Static Analysis

When developing and applying randomized algorithms, proper testing and checking of the source code is of utmost importance. If we apply a randomized metaheuristic to an optimization problem, then we usually do not which solution quality we can achieve. Therefore, we can usually not know whether we have implemented the algorithm correctly. In other words, detecting bugs is very hard. Unfortunately, this holds also for the components of the algorithms, such as the search operators, especially if they are randomized as well. A bug may lead to worse results and we might not even notice that the worse result quality is caused by the bug. We may think that the algorithm is just not working well on the problem.

Therefore, we need to test all components of the algorithm as far as we can. We can try check, for example, if a randomized nullary search operator indeed creates different solutions when invoked several times. We can try to check whether an algorithm fails with an exception. We can try to check whether the search operators create valid solutions and whether the algorithm passes valid solutions to the objective function. We can try to whether an objective function produces finite objective values and if bounds are specified for the objective values, we can check whether they indeed fall within these bounds. Now we cannot prove that there are no such bugs, due to the randomization. But by testing a few hundred times, we can at least detect very obvious and pathological bugs.

To ease such testing for you, we provide a set of tools for testing implemented algorithms, spaces, and operators in the package moptipyapps.tests. Here, you can find functions where you pass in instances of your implemented components and they are checked for compliance with the moptipy API and the problem setups defined in moptipyapps. In other words, if you go and implement your own algorithms, operators, and optimization problems, you can use our pre-defined unit tests to give them a thorough check before using them in production. Again, such tests cannot prove the absence of bugs. But they can at least give you a fair shot to detect pathological errors before wasting serious experimentation time.

We also try to extensively test our own code, see the coverage report of moptipy and moptipyapps.

Another way to try to improve and maintain code quality is to use static code analysis and type hints where possible and reasonable. A static analysis tool can inform you about, e.g., unused variables, which often result from a coding error. It can tell you if the types of expressions do not match, which usually indicates a coding error, too. It can tell you if you perform some security-wise unsafe operations (which is less often a problem in optimization, but it does not hurt to check). Code analysis tools can also help you to enforce best practices, which are good for performance, readability, and maintainability. They can push you to properly format and document your code, which, too, improve readability, maintainability, and usability. They even can detect a set of well-known and frequently-occurring bugs. We therefore also run a variety of such tools on our code base, including (in alphabetical order):

  • autoflake, a tool for finding unused imports and variables

  • bandit, a linter for finding security issues

  • dodgy, for checking for dodgy looking values in the code

  • flake8, a collection of linters

  • flake8-bugbear, for finding common bugs

  • flake8-eradicate, for finding commented-out code

  • flake8-use-fstring, for checking the correct use of f-strings

  • mypy, for checking types and type annotations

  • pycodestyle, for checking the formatting and coding style of the source

  • pydocstyle, for checking the format of the docstrings

  • pyflakes, for detecting some errors in the code

  • pylint, another static analysis tool

  • pyroma, for checking whether the code complies with various best practices

  • ruff, a static analysis tool checking a wide range of coding conventions

  • semgrep, another static analyzer for finding bugs and problems

  • tryceratops, for checking against exception handling anti-patterns

  • unimport, for checking against unused import statements

  • vulture, for finding dead code

On git pushes, GitHub also automatically runs CodeQL to check for common vulnerabilities and coding errors. We also turned on GitHub’s private vulnerability reporting and the Dependabot vulnerability and security alerts.

Using all of these tools increases the build time. However, combined with thorough unit testing and documentation, it should help to prevent bugs, to improve readability, maintainability, and usability of the code. It does not matter whether we are doing research or try to solve practical problems in the industry — we should always strive to make good software with high code quality.

Often, researchers in particular think that hacking something together that works is enough, that documentation is unimportant, that code style best practices can be ignored, and so on. And then they wonder why they cannot understand their own code a few years down the line (at least, this happened to me in the past…). Or why no one can use their code to build atop of their research (which is the normal case for me).

Improving code quality can never come later. We always must maintain high coding and documentation standards from the very beginning. While moptipy may still be far from achieving these goals, at least we try to get there.

Anyway, you can find our full build script running all the tests, doing all the static analyses, creating the documentation, and creating and packaging the distribution files in the repository, too. Besides the basic moptipyapps dependencies, it requires a set of additional dependencies. These are all automatically installed during the build procedure. The build only works under Linux.

5. License

moptipyapps is a library for implementing, using, and experimenting with metaheuristic optimization algorithms. Our project is developed for scientific, educational, and industrial applications.

Copyright (C) 2023 Thomas Weise (汤卫思教授)

Dr. Thomas Weise (see Contact) holds the copyright of this package except for the data of the benchmark sets we imported from other sources. moptipyapps is provided to the public as open source software under the GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007. Terms for other licenses, e.g., for specific industrial applications, can be negotiated with Dr. Thomas Weise (who can be reached via the contact information below).

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.

Please visit the contributions guidelines for moptipy if you would like to contribute to our package. If you have any concerns regarding security, please visit our security policy.

5.1. Exceptions

  • Most of the included benchmark instance data of the two-dimensional bin packing problem is taken from 2DPackLib. It has been stored in a more size-efficient way and some unnecessary information has been stripped from it (as we really only need the raw bin packing data). Nevertheless, the copyright of the original data lies with the authors 2DPackLib or the original authors of the datasets used by them.

  • The included benchmark instances for the Traveling Salesperson Problem are taken from TSPLib. The copyright of the original data lies with Gerhard Reinelt, the original author of TSPLib, or the original authors of the datasets used by him.

  • The included benchmark instances for the Traveling Tournament Problem are taken from RobinX. The copyright of the original data lies with the authors of the dataset, presumably D. Van Bulck, D. Goossens, J. Schönberger, and M. Guajardo.

  • The included benchmark instances for the Quadratic Assignment Problem are taken from QAPLib, which is available at https://qaplib.mgi.polymtl.ca and https://coral.ise.lehigh.edu/data-sets/qaplib. The copyright of the original repository lies with R.E. Burkard, E. Çela, S.E. Karisch and F. Rendl as well as Peter Hahn and Miguel Anjos.

6. Contact

If you have any questions or suggestions, please contact Prof. Dr. Thomas Weise (汤卫思教授) of the Institute of Applied Optimization (应用优化研究所, IAO) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) via email to tweise@hfuu.edu.cn with CC to tweise@ustc.edu.cn.

7. Modules and Code

diff --git a/make_sh.html b/make_sh.html new file mode 100644 index 00000000..18122ea9 --- /dev/null +++ b/make_sh.html @@ -0,0 +1,87 @@ +

#!/bin/bash
+
+# Make the moptipyapps package.
+
+# strict error handling
+set -o pipefail  # trace ERR through pipes
+set -o errtrace  # trace ERR through 'time command' and other functions
+set -o nounset   # set -u : exit the script if you try to use an uninitialized variable
+set -o errexit   # set -e : exit the script if any statement returns a non-true return value
+
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Welcome to the build script."
+
+export BUILD_SCRIPT="${BASH_SOURCE[0]}"
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): We set the environment variable BUILD_SCRIPT='$BUILD_SCRIPT'."
+
+currentDir="$(pwd)"
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): We are working in directory: '$currentDir'."
+
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Cleaning up old files."
+rm -rf *.whl
+find -type d -name "__pycache__" -prune -exec rm -rf {} \;
+find -type d -name ".ruff_cache" -prune -exec rm -rf {} \;
+rm -rf .mypy_cache
+rm -rf .ruff_cache
+rm -rf .pytest_cache
+rm -rf build
+rm -rf dist
+rm -rf docs/build
+rm -rf docs/source/*.rst
+rm -rf moptipyapps.egg-info
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Done cleaning up old files."
+
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): We setup a virtual environment in a temp directory."
+venvDir="$(mktemp -d)"
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Got temp dir '$venvDir', now creating environment in it."
+python3 -m venv --upgrade-deps --copies "$venvDir"
+
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Activating virtual environment in '$venvDir'."
+source "$venvDir/bin/activate"
+
+export PYTHON_INTERPRETER="$venvDir/bin/python3"
+oldPythonPath="${PYTHONPATH:-}"
+if [ -n "$oldPythonPath" ]; then
+  export PYTHONPATH="$currentDir:$oldPythonPath"
+else
+  export PYTHONPATH="$currentDir"
+fi
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): PYTHONPATH='$PYTHONPATH', PYTHON_INTERPRETER='$PYTHON_INTERPRETER'."
+
+cycle=1
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Installing requirements."
+while ! (timeout --kill-after=60m 58m "$PYTHON_INTERPRETER" -m pip install --no-input --default-timeout=300 --timeout=300 --retries=100 -r requirements.txt && timeout --kill-after=60m 58m "$PYTHON_INTERPRETER" -m pip install --no-input --default-timeout=300 --timeout=300 --retries=100 -r requirements-dev.txt) ; do
+    cycle=$((cycle+1))
+    if (("$cycle" > 100)) ; then
+        echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Something odd is happening: We have performed $cycle cycles of pip install and all failed. That's too many. Let's quit."
+        exit 2
+    fi
+    echo "$(date +'%0Y-%0m-%0d %0R:%0S'): pip install failed, we will try again."
+done
+
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Printing the list of installed packages."
+"$PYTHON_INTERPRETER" -m pip freeze
+
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Now performing unit tests."
+"$PYTHON_INTERPRETER" -m pycommons.dev.building.run_tests --package moptipyapps
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Finished running unit tests."
+
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Now performing static analysis."
+"$PYTHON_INTERPRETER" -m pycommons.dev.building.static_analysis --package moptipyapps
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Done: All static checks passed."
+
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Now building documentation."
+"$PYTHON_INTERPRETER" -m pycommons.dev.building.make_documentation --root "$currentDir" --package moptipyapps
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Done building documentation."
+
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Now building source distribution file."
+"$PYTHON_INTERPRETER" -m pycommons.dev.building.make_dist --root "$currentDir" --package moptipyapps
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Successfully finished building source distribution."
+
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Now trying to install moptipyapps."
+"$PYTHON_INTERPRETER" -m pip install --no-input --timeout 360 --retries 100 -v "$currentDir"
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): Successfully installed moptipyapps."
+
+
+echo "$(date +'%0Y-%0m-%0d %0R:%0S'): We have finished the build process."
+
diff --git a/modules.html b/modules.html new file mode 100644 index 00000000..b1e94441 --- /dev/null +++ b/modules.html @@ -0,0 +1 @@ +moptipyapps — moptipyapps 0.8.62 documentation

moptipyapps

diff --git a/moptipyapps.binpacking2d.encodings.html b/moptipyapps.binpacking2d.encodings.html new file mode 100644 index 00000000..da27aa86 --- /dev/null +++ b/moptipyapps.binpacking2d.encodings.html @@ -0,0 +1 @@ +moptipyapps.binpacking2d.encodings package — moptipyapps 0.8.62 documentation

moptipyapps.binpacking2d.encodings package

Different encodings for the two-dimensional bin packing problem.

The following encodings are implemented:

  • The improved bottom-left encoding 1, ibl_encoding_1, processes a permutation of objects from beginning to end and places the objects into the last bin (according to the improved-bottom-left method) until that bin is full and then begins to put them into the next bin.

  • The improved bottom-left encoding 2, ibl_encoding_2, processes a permutation of objects from beginning to end and places the objects into the first bin into which they fit (according to the improved-bottom-left method). It is slower than ibl_encoding_1 but its results are never worse for any permutation and better for several.

Important initial work on this code has been contributed by Mr. Rui ZHAO (赵睿), <zr1329142665@163.com> a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

Submodules

moptipyapps.binpacking2d.encodings.ibl_encoding_1 module

An improved bottom left encoding by Liu and Teng extended to multiple bins.

Here we provide an implementation of the improved bottom left encoding by Liu and Teng [1], but extended to bins with limited height. If the height of the bin is a limiting factor, then our implementation will automatically use multiple bins. Another implementation is given in moptipyapps.binpacking2d.encodings.ibl_encoding_2.

An instance of the two-dimensional bin packing problem defines a set of objects to be packed and a bin size (width and height). Each object to be packed has itself a width and a height as well as a repetition counter, which is 1 if the object only occurs a single time and larger otherwise (i.e., if the repetition counter is 5, the object needs to be packaged five times).

The encoding receives signed permutations with repetitions as input. Each element of the permutation identifies one object from the bin packing instance. Each such object ID must occur exactly as often as the repetition counter of the object in the instance data suggest. But the ID may also occur negated, in which case the object is supposed to rotated by 90°.

Now our encoding processes such a permutation from beginning to end. It starts with an empty bin 1. Each object is first placed with its right end at the right end of the bin and with its bottom line exactly at the top of the bin, i.e., outside of the bin. Then, in each step, we move the object as far down as possible. Then, we move it to the left as far as possible, but we immediately stop if there was another chance to move the object down. In other words, downward movements are preferred over left movements. This is repeated until no movement of the object is possible anymore.

Once the object cannot be moved anymore, we check if it is fully inside the bin. If yes, then the object is included in the bin and we continue with the next object. If not, it does not fit into the bin.

This is the “Improved Bottom Left” heuristic by Liu and Teng [1].

If the object does not fit into the current bin, we place it at the bottom-left corner of a new bin. We therefore increase the bin counter. From now on, all the following objects will be placed into this bin until the bin is full as well, in which case we move to the next bin again. This means that the current bin is closed at the same moment the first object is encountered that does not fit into it anymore. Therefore, the objects in a closed bin do no longer need to be considered when packing subsequent objects.

This is different from the second variant of this encoding implemented in file moptipyapps.binpacking2d.encodings.ibl_encoding_2, which always checks all the bins, starting at bin 1, when placing any object. That other encoding variant therefore must always consider all bins and is thus slower, but tends to yield better packings.

This procedure has originally been developed and implemented by Mr. Rui ZHAO (赵睿), <zr1329142665@163.com> a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

  1. Dequan Liu and Hongfei Teng. An Improved BL-Algorithm for Genetic Algorithm of the Orthogonal Packing of Rectangles. European Journal of Operational Research. 112(2):413-420. January (1999). https://doi.org/10.1016/S0377-2217(97)00437-2. http://www.paper.edu.cn/scholar/showpdf/MUT2AN0IOTD0Mxxh.

class moptipyapps.binpacking2d.encodings.ibl_encoding_1.ImprovedBottomLeftEncoding1(instance)[source]

Bases: Encoding

An Improved Bottem Left Encoding by Liu and Teng for multiple bins.

decode(x, y)[source]

Map a potentially signed permutation to a packing.

Parameters:
Return type:

None

log_parameters_to(logger)[source]

Log all parameters of this component as key-value pairs.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

moptipyapps.binpacking2d.encodings.ibl_encoding_2 module

An improved bottom left encoding by Liu and Teng extended to multiple bins.

Here we provide an implementation of the improved bottom left encoding by Liu and Teng [1], but extended to bins with limited height. If the height of the bin is a limiting factor, then our implementation will automatically use multiple bins. Another variant is given in moptipyapps.binpacking2d.encodings.ibl_encoding_1.

An instance instance of the two-dimensional bin packing problem defines a set of objects to be packed and a bin size (width and height). Each object to be packed has itself a width and a height as well as a repetition counter, which is 1 if the object only occurs a single time and larger otherwise (i.e., if the repetition counter is 5, the object needs to be packaged five times).

The encoding receives signed permutations with repetitions as input. Each element of the permutation identifies one object from the bin packing instance. Each such object ID must occur exactly as often as the repetition counter of the object in the instance data suggest. But the ID may also occur negated, in which case the object is supposed to rotated by 90°.

Now our encoding processes such a permutation from beginning to end. It starts with an empty bin 1. Each object is first placed with its right end at the right end of the first bin and with its bottom line exactly at the top of the bin, i.e., outside of the bin. Then, in each step, we move the object as far down as possible. Then, we move it to the left as far as possible, but we immediately stop if there was another chance to move the object down. In other words, downward movements are preferred over left movements. This is repeated until no movement of the object is possible anymore.

Once the object cannot be moved anymore, we check if it is fully inside the bin. If yes, then the object is included in the bin and we continue with the next object. If not, it does not fit into the bin.

This is the “Improved Bottom Left” heuristic by Liu and Teng [1].

If the object does not fit into the first bin and we already “opened” a second bin, then we try to place it into the second bin using the same procedure. And then into the third bin if that does not work out, and so on. Until we have tried unsuccessfully all the bins that we have opened.

In this case, we “open” the next bin and we place the object at the bottom-left corner of a new bin. Then we continue with the next object, again trying to put it into the first bin, then the second bin, and so on.

This is different from the first variant of this encoding implemented in file moptipyapps.binpacking2d.encodings.ibl_encoding_2, which always and only tries to put objects into the last bin that was opened (and moves to a new bin if that does not work out). That variant of the encoding is therefore faster than the one here, but the one here tends to yield better packings.

The problem with this is that we need to basically first try to put the object into the first bin. For this we need to look at all the objects that we have already placed, because it could be that we already have one object in the first bin, then one in the second bin that did not fit into the first bin, then one smaller object in the first bin again, and so on. If our new object does not fit into the first bin, then we need to do the same with the second bin, and so on. So for every bin we try, we need to look at all objects already placed. And the number of bins we could try could be equal to the number of objects that we have already placed (if each object occupies one bin alone). So we have a worst case complexity of O(n ** 2) for placing one object. And we do this for all objects, so we would have a O(n ** 3) overall complexity. Well, actually, it is worse: Because we repeat the process of looking at all the objects several times while moving our new item to the left and down and to the left and down. So I suspect that we actually have O(n ** 4). That is annoying.

We try to alleviate this a little bit by remembering, for each bin, the index of the first object that we put in there and the index of the last object we put in there. Now within these two indices, there also might be objects that we placed into other bins. But for a very little overhead (remembering two values per bin), we have a certain chance to speed up the process in several situations. For instance, the worst case from above, that each object occupies exactly one bin by itself becomes easier because we would only look at one already placed object per bin.

This procedure has originally been developed and implemented by Mr. Rui ZHAO (赵睿), <zr1329142665@163.com>, a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

  1. Dequan Liu and Hongfei Teng. An Improved BL-Algorithm for Genetic Algorithm of the Orthogonal Packing of Rectangles. European Journal of Operational Research. 112(2):413-420. January (1999). https://doi.org/10.1016/S0377-2217(97)00437-2. http://www.paper.edu.cn/scholar/showpdf/MUT2AN0IOTD0Mxxh.

class moptipyapps.binpacking2d.encodings.ibl_encoding_2.ImprovedBottomLeftEncoding2(instance)[source]

Bases: Encoding

An Improved Bottem Left Encoding by Liu and Teng for multiple bins.

decode(x, y)[source]

Map a potentially signed permutation to a packing.

Parameters:
Return type:

None

log_parameters_to(logger)[source]

Log all parameters of this component as key-value pairs.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

diff --git a/moptipyapps.binpacking2d.html b/moptipyapps.binpacking2d.html new file mode 100644 index 00000000..eb4771cb --- /dev/null +++ b/moptipyapps.binpacking2d.html @@ -0,0 +1,133 @@ +moptipyapps.binpacking2d package — moptipyapps 0.8.62 documentation

moptipyapps.binpacking2d package

Codes for the two-dimensional bin packing problem.

  • instance provides the instance data of two-dimensional bin packing problems. The instance data comprises the object sizes and multiplicities as well as the bin sizes. Several default instances from 2DPackLib (https://site.unibo.it/operations-research/en/research/2dpacklib) can be loaded from resources.

  • packing can store a packing, i.e., an assignment of objects to coordinates and bins. Such packings are solutions to the bin packing problem.

  • packing_space provides an implementation of the space interface for the packing objects. This class allows, for example, to instantiate the packings, to verify whether they are correct, and to convert them to and from strings.

  • plot_packing allows you to plot a packing.

  • ibl_encoding_1 is an implementation of the improved bottom-left encoding which closes bins immediately once an object does not fit.

  • ibl_encoding_2 is another implementation of the improved bottom-left encoding which tests each bin for each object.

  • bin_count_and_last_empty provides an objective function that tries to minimize the number of bins and pushes towards decreasing the number of objects in the very last bin used.

  • make_instances offers a method to download the two-dimensional bin packing instances from the original sources in the [2DPackLib](https://site.unibo.it/operations-research/en/research/2dpacklib) format.

Important initial work on this code has been contributed by Mr. Rui ZHAO (赵睿), <zr1329142665@163.com> a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

Subpackages

Submodules

moptipyapps.binpacking2d.bks module

In this file, we provide a set of best-known solutions for the 2D-BPP.

moptipyapps.binpacking2d.bks.ASQAS: Final[Element] = Element(name='asqas', name_suffix='', reference='vdBBMSB2016ASIASSTFI')

the asqas

moptipyapps.binpacking2d.bks.A_LARGE: Final[Element] = Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP')

the large A instances

moptipyapps.binpacking2d.bks.A_MED: Final[Element] = Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP')

the median sized A instances

moptipyapps.binpacking2d.bks.A_SMALL: Final[Element] = Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP')

the small A instances

moptipyapps.binpacking2d.bks.BENG_1_8: Final[Element] = Element(name='beng', name_suffix='1-8', reference='B1982PRPAHA')

the first part of the Beng instances

moptipyapps.binpacking2d.bks.BENG_9_10: Final[Element] = Element(name='beng', name_suffix='9-10', reference='B1982PRPAHA')

the second part of the Beng instances

moptipyapps.binpacking2d.bks.BRKGA_BPP_2R: Final[Element] = Element(name='BRKGA', name_suffix='BRKGABPPRTR', reference='GR2013ABRKGAF2A3BPP')

the BRKGA for 2D bin packing with rotation

moptipyapps.binpacking2d.bks.BRKGA_BPP_ANB: Final[Element] = Element(name='BRKGA', name_suffix='BRKGABPPRANB', reference='GR2013ABRKGAF2A3BPP')

the BRKGA for 2D bin packing without rotation

moptipyapps.binpacking2d.bks.CLASS_10_100: Final[Element] = Element(name='class 10', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 6-100 benchmarks

moptipyapps.binpacking2d.bks.CLASS_10_20: Final[Element] = Element(name='class 10', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 10-20 benchmarks

moptipyapps.binpacking2d.bks.CLASS_10_40: Final[Element] = Element(name='class 10', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 10-40 benchmarks

moptipyapps.binpacking2d.bks.CLASS_10_60: Final[Element] = Element(name='class 10', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 10-60 benchmarks

moptipyapps.binpacking2d.bks.CLASS_10_80: Final[Element] = Element(name='class 10', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 10-80 benchmarks

moptipyapps.binpacking2d.bks.CLASS_1_100: Final[Element] = Element(name='class 1', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 1-100 benchmarks

moptipyapps.binpacking2d.bks.CLASS_1_20: Final[Element] = Element(name='class 1', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 1-20 benchmarks

moptipyapps.binpacking2d.bks.CLASS_1_40: Final[Element] = Element(name='class 1', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 1-40 benchmarks

moptipyapps.binpacking2d.bks.CLASS_1_60: Final[Element] = Element(name='class 1', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 1-60 benchmarks

moptipyapps.binpacking2d.bks.CLASS_1_80: Final[Element] = Element(name='class 1', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 1-80 benchmarks

moptipyapps.binpacking2d.bks.CLASS_2_100: Final[Element] = Element(name='class 2', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 1-100 benchmarks

moptipyapps.binpacking2d.bks.CLASS_2_20: Final[Element] = Element(name='class 2', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 2-20 benchmarks

moptipyapps.binpacking2d.bks.CLASS_2_40: Final[Element] = Element(name='class 2', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 2-40 benchmarks

moptipyapps.binpacking2d.bks.CLASS_2_60: Final[Element] = Element(name='class 2', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 2-60 benchmarks

moptipyapps.binpacking2d.bks.CLASS_2_80: Final[Element] = Element(name='class 2', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 2-80 benchmarks

moptipyapps.binpacking2d.bks.CLASS_3_100: Final[Element] = Element(name='class 3', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 3-100 benchmarks

moptipyapps.binpacking2d.bks.CLASS_3_20: Final[Element] = Element(name='class 3', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 3-20 benchmarks

moptipyapps.binpacking2d.bks.CLASS_3_40: Final[Element] = Element(name='class 3', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 1-40 benchmarks

moptipyapps.binpacking2d.bks.CLASS_3_60: Final[Element] = Element(name='class 3', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 3-60 benchmarks

moptipyapps.binpacking2d.bks.CLASS_3_80: Final[Element] = Element(name='class 3', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 3-80 benchmarks

moptipyapps.binpacking2d.bks.CLASS_4_100: Final[Element] = Element(name='class 4', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 4-100 benchmarks

moptipyapps.binpacking2d.bks.CLASS_4_20: Final[Element] = Element(name='class 4', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 4-20 benchmarks

moptipyapps.binpacking2d.bks.CLASS_4_40: Final[Element] = Element(name='class 4', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 4-40 benchmarks

moptipyapps.binpacking2d.bks.CLASS_4_60: Final[Element] = Element(name='class 4', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 4-60 benchmarks

moptipyapps.binpacking2d.bks.CLASS_4_80: Final[Element] = Element(name='class 4', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 4-80 benchmarks

moptipyapps.binpacking2d.bks.CLASS_5_100: Final[Element] = Element(name='class 5', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 5-100 benchmarks

moptipyapps.binpacking2d.bks.CLASS_5_20: Final[Element] = Element(name='class 5', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 5-20 benchmarks

moptipyapps.binpacking2d.bks.CLASS_5_40: Final[Element] = Element(name='class 5', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 5-40 benchmarks

moptipyapps.binpacking2d.bks.CLASS_5_60: Final[Element] = Element(name='class 5', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 5-60 benchmarks

moptipyapps.binpacking2d.bks.CLASS_5_80: Final[Element] = Element(name='class 5', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 5-80 benchmarks

moptipyapps.binpacking2d.bks.CLASS_6_100: Final[Element] = Element(name='class 6', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 6-100 benchmarks

moptipyapps.binpacking2d.bks.CLASS_6_20: Final[Element] = Element(name='class 6', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 6-20 benchmarks

moptipyapps.binpacking2d.bks.CLASS_6_40: Final[Element] = Element(name='class 6', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 6-40 benchmarks

moptipyapps.binpacking2d.bks.CLASS_6_60: Final[Element] = Element(name='class 6', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 6-60 benchmarks

moptipyapps.binpacking2d.bks.CLASS_6_80: Final[Element] = Element(name='class 6', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 6-80 benchmarks

moptipyapps.binpacking2d.bks.CLASS_7_100: Final[Element] = Element(name='class 7', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 7-100 benchmarks

moptipyapps.binpacking2d.bks.CLASS_7_20: Final[Element] = Element(name='class 7', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 7-20 benchmarks

moptipyapps.binpacking2d.bks.CLASS_7_40: Final[Element] = Element(name='class 7', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 7-40 benchmarks

moptipyapps.binpacking2d.bks.CLASS_7_60: Final[Element] = Element(name='class 7', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 7-60 benchmarks

moptipyapps.binpacking2d.bks.CLASS_7_80: Final[Element] = Element(name='class 7', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 7-80 benchmarks

moptipyapps.binpacking2d.bks.CLASS_8_100: Final[Element] = Element(name='class 8', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 8-100 benchmarks

moptipyapps.binpacking2d.bks.CLASS_8_20: Final[Element] = Element(name='class 8', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 8-20 benchmarks

moptipyapps.binpacking2d.bks.CLASS_8_40: Final[Element] = Element(name='class 8', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 8-40 benchmarks

moptipyapps.binpacking2d.bks.CLASS_8_60: Final[Element] = Element(name='class 8', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 8-60 benchmarks

moptipyapps.binpacking2d.bks.CLASS_8_80: Final[Element] = Element(name='class 8', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 8-80 benchmarks

moptipyapps.binpacking2d.bks.CLASS_9_100: Final[Element] = Element(name='class 9', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 9-100 benchmarks

moptipyapps.binpacking2d.bks.CLASS_9_20: Final[Element] = Element(name='class 9', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 9-20 benchmarks

moptipyapps.binpacking2d.bks.CLASS_9_40: Final[Element] = Element(name='class 9', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 9-40 benchmarks

moptipyapps.binpacking2d.bks.CLASS_9_60: Final[Element] = Element(name='class 9', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 9-60 benchmarks

moptipyapps.binpacking2d.bks.CLASS_9_80: Final[Element] = Element(name='class 9', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP')

the class 9-80 benchmarks

moptipyapps.binpacking2d.bks.CLASS_AND_BENG: Final[tuple[Element, ...]] = (Element(name='beng', name_suffix='1-8', reference='B1982PRPAHA'), Element(name='beng', name_suffix='9-10', reference='B1982PRPAHA'), Element(name='class 1', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 1', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 1', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 1', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 1', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 2', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 2', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 2', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 2', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 2', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 3', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 3', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 3', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 3', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 3', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 4', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 4', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 4', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 4', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 4', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 5', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 5', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 5', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 5', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 5', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 6', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 6', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 6', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 6', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 6', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 7', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 7', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 7', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 7', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 7', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 8', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 8', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 8', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 8', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 8', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 9', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 9', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 9', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 9', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 9', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 10', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 10', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 10', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 10', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), Element(name='class 10', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'))

The set of class and beng instance groups

moptipyapps.binpacking2d.bks.EALGFI: Final[Element] = Element(name='EALGFIEA', name_suffix='EALGFILGFI', reference='BS2013ST2BPPBMOAHEA')

the LGFi algorithm

class moptipyapps.binpacking2d.bks.Element(name, name_suffix, reference='{{NO_REF}}')[source]

Bases: object

The algorithm or instance group specification.

get_bibtex()[source]

Get the BibTeX for this element.

Return type:

Iterable[str]

Returns:

the BibTeX string

name: str

the name or name prefix

name_suffix: str

the name suffix, if any, and the empty string otherwise

reference: str

the reference to the related work

moptipyapps.binpacking2d.bks.GRASP_VND: Final[Element] = Element(name='GRASPVNDGRASP', name_suffix='GRASPVNDVND', reference='PAVOT2010AHGVAFTATDBP')

the grasp/vnd for 2D bin packing without rotation

moptipyapps.binpacking2d.bks.GROUPS_TO_INSTANCES: Final[Mapping[Element, tuple[str, ...]]] = mappingproxy({Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'): ('a01', 'a02', 'a03', 'a04', 'a06', 'a08', 'a10', 'a12', 'a22', 'a30', 'a34', 'a39', 'a42', 'a43'), Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'): ('a05', 'a07', 'a17', 'a25', 'a26', 'a27', 'a28', 'a31', 'a35', 'a36', 'a37', 'a38', 'a40', 'a41'), Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'): ('a09', 'a11', 'a13', 'a14', 'a15', 'a16', 'a18', 'a19', 'a20', 'a21', 'a23', 'a24', 'a29', 'a32', 'a33'), Element(name='beng', name_suffix='1-8', reference='B1982PRPAHA'): ('beng01', 'beng02', 'beng03', 'beng04', 'beng05', 'beng06', 'beng07', 'beng08'), Element(name='beng', name_suffix='9-10', reference='B1982PRPAHA'): ('beng09', 'beng10'), Element(name='class 1', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl01_020_01', 'cl01_020_02', 'cl01_020_03', 'cl01_020_04', 'cl01_020_05', 'cl01_020_06', 'cl01_020_07', 'cl01_020_08', 'cl01_020_09', 'cl01_020_10'), Element(name='class 1', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl01_040_01', 'cl01_040_02', 'cl01_040_03', 'cl01_040_04', 'cl01_040_05', 'cl01_040_06', 'cl01_040_07', 'cl01_040_08', 'cl01_040_09', 'cl01_040_10'), Element(name='class 1', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl01_060_01', 'cl01_060_02', 'cl01_060_03', 'cl01_060_04', 'cl01_060_05', 'cl01_060_06', 'cl01_060_07', 'cl01_060_08', 'cl01_060_09', 'cl01_060_10'), Element(name='class 1', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl01_080_01', 'cl01_080_02', 'cl01_080_03', 'cl01_080_04', 'cl01_080_05', 'cl01_080_06', 'cl01_080_07', 'cl01_080_08', 'cl01_080_09', 'cl01_080_10'), Element(name='class 1', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl01_100_01', 'cl01_100_02', 'cl01_100_03', 'cl01_100_04', 'cl01_100_05', 'cl01_100_06', 'cl01_100_07', 'cl01_100_08', 'cl01_100_09', 'cl01_100_10'), Element(name='class 2', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl02_020_01', 'cl02_020_02', 'cl02_020_03', 'cl02_020_04', 'cl02_020_05', 'cl02_020_06', 'cl02_020_07', 'cl02_020_08', 'cl02_020_09', 'cl02_020_10'), Element(name='class 2', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl02_040_01', 'cl02_040_02', 'cl02_040_03', 'cl02_040_04', 'cl02_040_05', 'cl02_040_06', 'cl02_040_07', 'cl02_040_08', 'cl02_040_09', 'cl02_040_10'), Element(name='class 2', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl02_060_01', 'cl02_060_02', 'cl02_060_03', 'cl02_060_04', 'cl02_060_05', 'cl02_060_06', 'cl02_060_07', 'cl02_060_08', 'cl02_060_09', 'cl02_060_10'), Element(name='class 2', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl02_080_01', 'cl02_080_02', 'cl02_080_03', 'cl02_080_04', 'cl02_080_05', 'cl02_080_06', 'cl02_080_07', 'cl02_080_08', 'cl02_080_09', 'cl02_080_10'), Element(name='class 2', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl02_100_01', 'cl02_100_02', 'cl02_100_03', 'cl02_100_04', 'cl02_100_05', 'cl02_100_06', 'cl02_100_07', 'cl02_100_08', 'cl02_100_09', 'cl02_100_10'), Element(name='class 3', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl03_020_01', 'cl03_020_02', 'cl03_020_03', 'cl03_020_04', 'cl03_020_05', 'cl03_020_06', 'cl03_020_07', 'cl03_020_08', 'cl03_020_09', 'cl03_020_10'), Element(name='class 3', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl03_040_01', 'cl03_040_02', 'cl03_040_03', 'cl03_040_04', 'cl03_040_05', 'cl03_040_06', 'cl03_040_07', 'cl03_040_08', 'cl03_040_09', 'cl03_040_10'), Element(name='class 3', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl03_060_01', 'cl03_060_02', 'cl03_060_03', 'cl03_060_04', 'cl03_060_05', 'cl03_060_06', 'cl03_060_07', 'cl03_060_08', 'cl03_060_09', 'cl03_060_10'), Element(name='class 3', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl03_080_01', 'cl03_080_02', 'cl03_080_03', 'cl03_080_04', 'cl03_080_05', 'cl03_080_06', 'cl03_080_07', 'cl03_080_08', 'cl03_080_09', 'cl03_080_10'), Element(name='class 3', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl03_100_01', 'cl03_100_02', 'cl03_100_03', 'cl03_100_04', 'cl03_100_05', 'cl03_100_06', 'cl03_100_07', 'cl03_100_08', 'cl03_100_09', 'cl03_100_10'), Element(name='class 4', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl04_020_01', 'cl04_020_02', 'cl04_020_03', 'cl04_020_04', 'cl04_020_05', 'cl04_020_06', 'cl04_020_07', 'cl04_020_08', 'cl04_020_09', 'cl04_020_10'), Element(name='class 4', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl04_040_01', 'cl04_040_02', 'cl04_040_03', 'cl04_040_04', 'cl04_040_05', 'cl04_040_06', 'cl04_040_07', 'cl04_040_08', 'cl04_040_09', 'cl04_040_10'), Element(name='class 4', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl04_060_01', 'cl04_060_02', 'cl04_060_03', 'cl04_060_04', 'cl04_060_05', 'cl04_060_06', 'cl04_060_07', 'cl04_060_08', 'cl04_060_09', 'cl04_060_10'), Element(name='class 4', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl04_080_01', 'cl04_080_02', 'cl04_080_03', 'cl04_080_04', 'cl04_080_05', 'cl04_080_06', 'cl04_080_07', 'cl04_080_08', 'cl04_080_09', 'cl04_080_10'), Element(name='class 4', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl04_100_01', 'cl04_100_02', 'cl04_100_03', 'cl04_100_04', 'cl04_100_05', 'cl04_100_06', 'cl04_100_07', 'cl04_100_08', 'cl04_100_09', 'cl04_100_10'), Element(name='class 5', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl05_020_01', 'cl05_020_02', 'cl05_020_03', 'cl05_020_04', 'cl05_020_05', 'cl05_020_06', 'cl05_020_07', 'cl05_020_08', 'cl05_020_09', 'cl05_020_10'), Element(name='class 5', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl05_040_01', 'cl05_040_02', 'cl05_040_03', 'cl05_040_04', 'cl05_040_05', 'cl05_040_06', 'cl05_040_07', 'cl05_040_08', 'cl05_040_09', 'cl05_040_10'), Element(name='class 5', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl05_060_01', 'cl05_060_02', 'cl05_060_03', 'cl05_060_04', 'cl05_060_05', 'cl05_060_06', 'cl05_060_07', 'cl05_060_08', 'cl05_060_09', 'cl05_060_10'), Element(name='class 5', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl05_080_01', 'cl05_080_02', 'cl05_080_03', 'cl05_080_04', 'cl05_080_05', 'cl05_080_06', 'cl05_080_07', 'cl05_080_08', 'cl05_080_09', 'cl05_080_10'), Element(name='class 5', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl05_100_01', 'cl05_100_02', 'cl05_100_03', 'cl05_100_04', 'cl05_100_05', 'cl05_100_06', 'cl05_100_07', 'cl05_100_08', 'cl05_100_09', 'cl05_100_10'), Element(name='class 6', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl06_020_01', 'cl06_020_02', 'cl06_020_03', 'cl06_020_04', 'cl06_020_05', 'cl06_020_06', 'cl06_020_07', 'cl06_020_08', 'cl06_020_09', 'cl06_020_10'), Element(name='class 6', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl06_040_01', 'cl06_040_02', 'cl06_040_03', 'cl06_040_04', 'cl06_040_05', 'cl06_040_06', 'cl06_040_07', 'cl06_040_08', 'cl06_040_09', 'cl06_040_10'), Element(name='class 6', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl06_060_01', 'cl06_060_02', 'cl06_060_03', 'cl06_060_04', 'cl06_060_05', 'cl06_060_06', 'cl06_060_07', 'cl06_060_08', 'cl06_060_09', 'cl06_060_10'), Element(name='class 6', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl06_080_01', 'cl06_080_02', 'cl06_080_03', 'cl06_080_04', 'cl06_080_05', 'cl06_080_06', 'cl06_080_07', 'cl06_080_08', 'cl06_080_09', 'cl06_080_10'), Element(name='class 6', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl06_100_01', 'cl06_100_02', 'cl06_100_03', 'cl06_100_04', 'cl06_100_05', 'cl06_100_06', 'cl06_100_07', 'cl06_100_08', 'cl06_100_09', 'cl06_100_10'), Element(name='class 7', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl07_020_01', 'cl07_020_02', 'cl07_020_03', 'cl07_020_04', 'cl07_020_05', 'cl07_020_06', 'cl07_020_07', 'cl07_020_08', 'cl07_020_09', 'cl07_020_10'), Element(name='class 7', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl07_040_01', 'cl07_040_02', 'cl07_040_03', 'cl07_040_04', 'cl07_040_05', 'cl07_040_06', 'cl07_040_07', 'cl07_040_08', 'cl07_040_09', 'cl07_040_10'), Element(name='class 7', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl07_060_01', 'cl07_060_02', 'cl07_060_03', 'cl07_060_04', 'cl07_060_05', 'cl07_060_06', 'cl07_060_07', 'cl07_060_08', 'cl07_060_09', 'cl07_060_10'), Element(name='class 7', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl07_080_01', 'cl07_080_02', 'cl07_080_03', 'cl07_080_04', 'cl07_080_05', 'cl07_080_06', 'cl07_080_07', 'cl07_080_08', 'cl07_080_09', 'cl07_080_10'), Element(name='class 7', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl07_100_01', 'cl07_100_02', 'cl07_100_03', 'cl07_100_04', 'cl07_100_05', 'cl07_100_06', 'cl07_100_07', 'cl07_100_08', 'cl07_100_09', 'cl07_100_10'), Element(name='class 8', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl08_020_01', 'cl08_020_02', 'cl08_020_03', 'cl08_020_04', 'cl08_020_05', 'cl08_020_06', 'cl08_020_07', 'cl08_020_08', 'cl08_020_09', 'cl08_020_10'), Element(name='class 8', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl08_040_01', 'cl08_040_02', 'cl08_040_03', 'cl08_040_04', 'cl08_040_05', 'cl08_040_06', 'cl08_040_07', 'cl08_040_08', 'cl08_040_09', 'cl08_040_10'), Element(name='class 8', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl08_060_01', 'cl08_060_02', 'cl08_060_03', 'cl08_060_04', 'cl08_060_05', 'cl08_060_06', 'cl08_060_07', 'cl08_060_08', 'cl08_060_09', 'cl08_060_10'), Element(name='class 8', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl08_080_01', 'cl08_080_02', 'cl08_080_03', 'cl08_080_04', 'cl08_080_05', 'cl08_080_06', 'cl08_080_07', 'cl08_080_08', 'cl08_080_09', 'cl08_080_10'), Element(name='class 8', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl08_100_01', 'cl08_100_02', 'cl08_100_03', 'cl08_100_04', 'cl08_100_05', 'cl08_100_06', 'cl08_100_07', 'cl08_100_08', 'cl08_100_09', 'cl08_100_10'), Element(name='class 9', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl09_020_01', 'cl09_020_02', 'cl09_020_03', 'cl09_020_04', 'cl09_020_05', 'cl09_020_06', 'cl09_020_07', 'cl09_020_08', 'cl09_020_09', 'cl09_020_10'), Element(name='class 9', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl09_040_01', 'cl09_040_02', 'cl09_040_03', 'cl09_040_04', 'cl09_040_05', 'cl09_040_06', 'cl09_040_07', 'cl09_040_08', 'cl09_040_09', 'cl09_040_10'), Element(name='class 9', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl09_060_01', 'cl09_060_02', 'cl09_060_03', 'cl09_060_04', 'cl09_060_05', 'cl09_060_06', 'cl09_060_07', 'cl09_060_08', 'cl09_060_09', 'cl09_060_10'), Element(name='class 9', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl09_080_01', 'cl09_080_02', 'cl09_080_03', 'cl09_080_04', 'cl09_080_05', 'cl09_080_06', 'cl09_080_07', 'cl09_080_08', 'cl09_080_09', 'cl09_080_10'), Element(name='class 9', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl09_100_01', 'cl09_100_02', 'cl09_100_03', 'cl09_100_04', 'cl09_100_05', 'cl09_100_06', 'cl09_100_07', 'cl09_100_08', 'cl09_100_09', 'cl09_100_10'), Element(name='class 10', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl10_020_01', 'cl10_020_02', 'cl10_020_03', 'cl10_020_04', 'cl10_020_05', 'cl10_020_06', 'cl10_020_07', 'cl10_020_08', 'cl10_020_09', 'cl10_020_10'), Element(name='class 10', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl10_040_01', 'cl10_040_02', 'cl10_040_03', 'cl10_040_04', 'cl10_040_05', 'cl10_040_06', 'cl10_040_07', 'cl10_040_08', 'cl10_040_09', 'cl10_040_10'), Element(name='class 10', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl10_060_01', 'cl10_060_02', 'cl10_060_03', 'cl10_060_04', 'cl10_060_05', 'cl10_060_06', 'cl10_060_07', 'cl10_060_08', 'cl10_060_09', 'cl10_060_10'), Element(name='class 10', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl10_080_01', 'cl10_080_02', 'cl10_080_03', 'cl10_080_04', 'cl10_080_05', 'cl10_080_06', 'cl10_080_07', 'cl10_080_08', 'cl10_080_09', 'cl10_080_10'), Element(name='class 10', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'): ('cl10_100_01', 'cl10_100_02', 'cl10_100_03', 'cl10_100_04', 'cl10_100_05', 'cl10_100_06', 'cl10_100_07', 'cl10_100_08', 'cl10_100_09', 'cl10_100_10'), Element(name='asqas', name_suffix='', reference='vdBBMSB2016ASIASSTFI'): ('asqas03', 'asqas08', 'asqas20', 'asqas34')})

a mapping of instance groups to instances

moptipyapps.binpacking2d.bks.HHANO_R: Final[Element] = Element(name='HHANO', name_suffix='HHANOR', reference='BDC2015RHHAFTOONO2BPP')

the HHANO-R algorithm for 2D bin packing with rotation

moptipyapps.binpacking2d.bks.HHANO_SR: Final[Element] = Element(name='HHANO', name_suffix='HHANOSR', reference='BDC2015RHHAFTOONO2BPP')

the HHANO-SR algorithm for 2D bin packing with rotation

moptipyapps.binpacking2d.bks.INSTANCES_TO_GROUPS: Final[Mapping[str, Element]] = mappingproxy({'a01': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'a02': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'a03': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'a04': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'a05': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a06': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'a07': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a08': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'a09': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a10': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'a11': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a12': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'a13': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a14': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a15': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a16': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a17': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a18': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a19': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a20': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a21': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a22': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'a23': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a24': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a25': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a26': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a27': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a28': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a29': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a30': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'a31': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a32': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a33': Element(name='a', name_suffix='large', reference='MAVdC2010AFMFTTDGCSP'), 'a34': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'a35': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a36': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a37': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a38': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a39': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'a40': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a41': Element(name='a', name_suffix='med', reference='MAVdC2010AFMFTTDGCSP'), 'a42': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'a43': Element(name='a', name_suffix='small', reference='MAVdC2010AFMFTTDGCSP'), 'beng01': Element(name='beng', name_suffix='1-8', reference='B1982PRPAHA'), 'beng02': Element(name='beng', name_suffix='1-8', reference='B1982PRPAHA'), 'beng03': Element(name='beng', name_suffix='1-8', reference='B1982PRPAHA'), 'beng04': Element(name='beng', name_suffix='1-8', reference='B1982PRPAHA'), 'beng05': Element(name='beng', name_suffix='1-8', reference='B1982PRPAHA'), 'beng06': Element(name='beng', name_suffix='1-8', reference='B1982PRPAHA'), 'beng07': Element(name='beng', name_suffix='1-8', reference='B1982PRPAHA'), 'beng08': Element(name='beng', name_suffix='1-8', reference='B1982PRPAHA'), 'beng09': Element(name='beng', name_suffix='9-10', reference='B1982PRPAHA'), 'beng10': Element(name='beng', name_suffix='9-10', reference='B1982PRPAHA'), 'cl01_020_01': Element(name='class 1', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_020_02': Element(name='class 1', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_020_03': Element(name='class 1', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_020_04': Element(name='class 1', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_020_05': Element(name='class 1', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_020_06': Element(name='class 1', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_020_07': Element(name='class 1', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_020_08': Element(name='class 1', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_020_09': Element(name='class 1', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_020_10': Element(name='class 1', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_040_01': Element(name='class 1', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_040_02': Element(name='class 1', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_040_03': Element(name='class 1', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_040_04': Element(name='class 1', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_040_05': Element(name='class 1', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_040_06': Element(name='class 1', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_040_07': Element(name='class 1', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_040_08': Element(name='class 1', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_040_09': Element(name='class 1', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_040_10': Element(name='class 1', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_060_01': Element(name='class 1', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_060_02': Element(name='class 1', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_060_03': Element(name='class 1', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_060_04': Element(name='class 1', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_060_05': Element(name='class 1', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_060_06': Element(name='class 1', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_060_07': Element(name='class 1', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_060_08': Element(name='class 1', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_060_09': Element(name='class 1', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_060_10': Element(name='class 1', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_080_01': Element(name='class 1', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_080_02': Element(name='class 1', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_080_03': Element(name='class 1', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_080_04': Element(name='class 1', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_080_05': Element(name='class 1', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_080_06': Element(name='class 1', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_080_07': Element(name='class 1', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_080_08': Element(name='class 1', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_080_09': Element(name='class 1', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_080_10': Element(name='class 1', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_100_01': Element(name='class 1', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_100_02': Element(name='class 1', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_100_03': Element(name='class 1', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_100_04': Element(name='class 1', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_100_05': Element(name='class 1', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_100_06': Element(name='class 1', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_100_07': Element(name='class 1', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_100_08': Element(name='class 1', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_100_09': Element(name='class 1', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl01_100_10': Element(name='class 1', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_020_01': Element(name='class 2', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_020_02': Element(name='class 2', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_020_03': Element(name='class 2', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_020_04': Element(name='class 2', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_020_05': Element(name='class 2', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_020_06': Element(name='class 2', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_020_07': Element(name='class 2', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_020_08': Element(name='class 2', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_020_09': Element(name='class 2', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_020_10': Element(name='class 2', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_040_01': Element(name='class 2', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_040_02': Element(name='class 2', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_040_03': Element(name='class 2', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_040_04': Element(name='class 2', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_040_05': Element(name='class 2', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_040_06': Element(name='class 2', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_040_07': Element(name='class 2', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_040_08': Element(name='class 2', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_040_09': Element(name='class 2', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_040_10': Element(name='class 2', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_060_01': Element(name='class 2', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_060_02': Element(name='class 2', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_060_03': Element(name='class 2', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_060_04': Element(name='class 2', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_060_05': Element(name='class 2', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_060_06': Element(name='class 2', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_060_07': Element(name='class 2', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_060_08': Element(name='class 2', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_060_09': Element(name='class 2', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_060_10': Element(name='class 2', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_080_01': Element(name='class 2', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_080_02': Element(name='class 2', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_080_03': Element(name='class 2', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_080_04': Element(name='class 2', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_080_05': Element(name='class 2', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_080_06': Element(name='class 2', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_080_07': Element(name='class 2', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_080_08': Element(name='class 2', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_080_09': Element(name='class 2', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_080_10': Element(name='class 2', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_100_01': Element(name='class 2', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_100_02': Element(name='class 2', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_100_03': Element(name='class 2', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_100_04': Element(name='class 2', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_100_05': Element(name='class 2', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_100_06': Element(name='class 2', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_100_07': Element(name='class 2', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_100_08': Element(name='class 2', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_100_09': Element(name='class 2', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl02_100_10': Element(name='class 2', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_020_01': Element(name='class 3', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_020_02': Element(name='class 3', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_020_03': Element(name='class 3', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_020_04': Element(name='class 3', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_020_05': Element(name='class 3', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_020_06': Element(name='class 3', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_020_07': Element(name='class 3', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_020_08': Element(name='class 3', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_020_09': Element(name='class 3', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_020_10': Element(name='class 3', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_040_01': Element(name='class 3', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_040_02': Element(name='class 3', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_040_03': Element(name='class 3', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_040_04': Element(name='class 3', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_040_05': Element(name='class 3', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_040_06': Element(name='class 3', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_040_07': Element(name='class 3', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_040_08': Element(name='class 3', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_040_09': Element(name='class 3', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_040_10': Element(name='class 3', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_060_01': Element(name='class 3', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_060_02': Element(name='class 3', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_060_03': Element(name='class 3', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_060_04': Element(name='class 3', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_060_05': Element(name='class 3', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_060_06': Element(name='class 3', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_060_07': Element(name='class 3', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_060_08': Element(name='class 3', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_060_09': Element(name='class 3', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_060_10': Element(name='class 3', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_080_01': Element(name='class 3', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_080_02': Element(name='class 3', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_080_03': Element(name='class 3', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_080_04': Element(name='class 3', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_080_05': Element(name='class 3', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_080_06': Element(name='class 3', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_080_07': Element(name='class 3', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_080_08': Element(name='class 3', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_080_09': Element(name='class 3', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_080_10': Element(name='class 3', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_100_01': Element(name='class 3', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_100_02': Element(name='class 3', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_100_03': Element(name='class 3', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_100_04': Element(name='class 3', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_100_05': Element(name='class 3', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_100_06': Element(name='class 3', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_100_07': Element(name='class 3', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_100_08': Element(name='class 3', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_100_09': Element(name='class 3', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl03_100_10': Element(name='class 3', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_020_01': Element(name='class 4', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_020_02': Element(name='class 4', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_020_03': Element(name='class 4', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_020_04': Element(name='class 4', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_020_05': Element(name='class 4', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_020_06': Element(name='class 4', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_020_07': Element(name='class 4', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_020_08': Element(name='class 4', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_020_09': Element(name='class 4', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_020_10': Element(name='class 4', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_040_01': Element(name='class 4', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_040_02': Element(name='class 4', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_040_03': Element(name='class 4', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_040_04': Element(name='class 4', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_040_05': Element(name='class 4', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_040_06': Element(name='class 4', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_040_07': Element(name='class 4', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_040_08': Element(name='class 4', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_040_09': Element(name='class 4', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_040_10': Element(name='class 4', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_060_01': Element(name='class 4', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_060_02': Element(name='class 4', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_060_03': Element(name='class 4', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_060_04': Element(name='class 4', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_060_05': Element(name='class 4', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_060_06': Element(name='class 4', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_060_07': Element(name='class 4', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_060_08': Element(name='class 4', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_060_09': Element(name='class 4', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_060_10': Element(name='class 4', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_080_01': Element(name='class 4', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_080_02': Element(name='class 4', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_080_03': Element(name='class 4', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_080_04': Element(name='class 4', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_080_05': Element(name='class 4', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_080_06': Element(name='class 4', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_080_07': Element(name='class 4', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_080_08': Element(name='class 4', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_080_09': Element(name='class 4', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_080_10': Element(name='class 4', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_100_01': Element(name='class 4', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_100_02': Element(name='class 4', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_100_03': Element(name='class 4', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_100_04': Element(name='class 4', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_100_05': Element(name='class 4', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_100_06': Element(name='class 4', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_100_07': Element(name='class 4', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_100_08': Element(name='class 4', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_100_09': Element(name='class 4', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl04_100_10': Element(name='class 4', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_020_01': Element(name='class 5', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_020_02': Element(name='class 5', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_020_03': Element(name='class 5', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_020_04': Element(name='class 5', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_020_05': Element(name='class 5', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_020_06': Element(name='class 5', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_020_07': Element(name='class 5', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_020_08': Element(name='class 5', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_020_09': Element(name='class 5', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_020_10': Element(name='class 5', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_040_01': Element(name='class 5', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_040_02': Element(name='class 5', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_040_03': Element(name='class 5', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_040_04': Element(name='class 5', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_040_05': Element(name='class 5', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_040_06': Element(name='class 5', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_040_07': Element(name='class 5', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_040_08': Element(name='class 5', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_040_09': Element(name='class 5', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_040_10': Element(name='class 5', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_060_01': Element(name='class 5', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_060_02': Element(name='class 5', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_060_03': Element(name='class 5', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_060_04': Element(name='class 5', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_060_05': Element(name='class 5', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_060_06': Element(name='class 5', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_060_07': Element(name='class 5', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_060_08': Element(name='class 5', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_060_09': Element(name='class 5', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_060_10': Element(name='class 5', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_080_01': Element(name='class 5', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_080_02': Element(name='class 5', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_080_03': Element(name='class 5', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_080_04': Element(name='class 5', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_080_05': Element(name='class 5', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_080_06': Element(name='class 5', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_080_07': Element(name='class 5', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_080_08': Element(name='class 5', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_080_09': Element(name='class 5', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_080_10': Element(name='class 5', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_100_01': Element(name='class 5', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_100_02': Element(name='class 5', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_100_03': Element(name='class 5', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_100_04': Element(name='class 5', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_100_05': Element(name='class 5', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_100_06': Element(name='class 5', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_100_07': Element(name='class 5', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_100_08': Element(name='class 5', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_100_09': Element(name='class 5', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl05_100_10': Element(name='class 5', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_020_01': Element(name='class 6', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_020_02': Element(name='class 6', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_020_03': Element(name='class 6', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_020_04': Element(name='class 6', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_020_05': Element(name='class 6', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_020_06': Element(name='class 6', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_020_07': Element(name='class 6', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_020_08': Element(name='class 6', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_020_09': Element(name='class 6', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_020_10': Element(name='class 6', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_040_01': Element(name='class 6', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_040_02': Element(name='class 6', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_040_03': Element(name='class 6', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_040_04': Element(name='class 6', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_040_05': Element(name='class 6', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_040_06': Element(name='class 6', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_040_07': Element(name='class 6', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_040_08': Element(name='class 6', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_040_09': Element(name='class 6', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_040_10': Element(name='class 6', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_060_01': Element(name='class 6', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_060_02': Element(name='class 6', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_060_03': Element(name='class 6', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_060_04': Element(name='class 6', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_060_05': Element(name='class 6', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_060_06': Element(name='class 6', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_060_07': Element(name='class 6', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_060_08': Element(name='class 6', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_060_09': Element(name='class 6', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_060_10': Element(name='class 6', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_080_01': Element(name='class 6', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_080_02': Element(name='class 6', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_080_03': Element(name='class 6', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_080_04': Element(name='class 6', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_080_05': Element(name='class 6', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_080_06': Element(name='class 6', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_080_07': Element(name='class 6', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_080_08': Element(name='class 6', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_080_09': Element(name='class 6', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_080_10': Element(name='class 6', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_100_01': Element(name='class 6', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_100_02': Element(name='class 6', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_100_03': Element(name='class 6', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_100_04': Element(name='class 6', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_100_05': Element(name='class 6', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_100_06': Element(name='class 6', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_100_07': Element(name='class 6', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_100_08': Element(name='class 6', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_100_09': Element(name='class 6', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl06_100_10': Element(name='class 6', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_020_01': Element(name='class 7', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_020_02': Element(name='class 7', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_020_03': Element(name='class 7', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_020_04': Element(name='class 7', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_020_05': Element(name='class 7', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_020_06': Element(name='class 7', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_020_07': Element(name='class 7', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_020_08': Element(name='class 7', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_020_09': Element(name='class 7', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_020_10': Element(name='class 7', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_040_01': Element(name='class 7', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_040_02': Element(name='class 7', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_040_03': Element(name='class 7', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_040_04': Element(name='class 7', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_040_05': Element(name='class 7', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_040_06': Element(name='class 7', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_040_07': Element(name='class 7', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_040_08': Element(name='class 7', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_040_09': Element(name='class 7', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_040_10': Element(name='class 7', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_060_01': Element(name='class 7', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_060_02': Element(name='class 7', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_060_03': Element(name='class 7', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_060_04': Element(name='class 7', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_060_05': Element(name='class 7', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_060_06': Element(name='class 7', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_060_07': Element(name='class 7', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_060_08': Element(name='class 7', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_060_09': Element(name='class 7', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_060_10': Element(name='class 7', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_080_01': Element(name='class 7', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_080_02': Element(name='class 7', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_080_03': Element(name='class 7', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_080_04': Element(name='class 7', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_080_05': Element(name='class 7', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_080_06': Element(name='class 7', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_080_07': Element(name='class 7', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_080_08': Element(name='class 7', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_080_09': Element(name='class 7', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_080_10': Element(name='class 7', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_100_01': Element(name='class 7', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_100_02': Element(name='class 7', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_100_03': Element(name='class 7', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_100_04': Element(name='class 7', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_100_05': Element(name='class 7', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_100_06': Element(name='class 7', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_100_07': Element(name='class 7', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_100_08': Element(name='class 7', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_100_09': Element(name='class 7', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl07_100_10': Element(name='class 7', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_020_01': Element(name='class 8', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_020_02': Element(name='class 8', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_020_03': Element(name='class 8', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_020_04': Element(name='class 8', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_020_05': Element(name='class 8', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_020_06': Element(name='class 8', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_020_07': Element(name='class 8', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_020_08': Element(name='class 8', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_020_09': Element(name='class 8', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_020_10': Element(name='class 8', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_040_01': Element(name='class 8', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_040_02': Element(name='class 8', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_040_03': Element(name='class 8', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_040_04': Element(name='class 8', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_040_05': Element(name='class 8', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_040_06': Element(name='class 8', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_040_07': Element(name='class 8', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_040_08': Element(name='class 8', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_040_09': Element(name='class 8', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_040_10': Element(name='class 8', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_060_01': Element(name='class 8', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_060_02': Element(name='class 8', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_060_03': Element(name='class 8', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_060_04': Element(name='class 8', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_060_05': Element(name='class 8', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_060_06': Element(name='class 8', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_060_07': Element(name='class 8', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_060_08': Element(name='class 8', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_060_09': Element(name='class 8', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_060_10': Element(name='class 8', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_080_01': Element(name='class 8', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_080_02': Element(name='class 8', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_080_03': Element(name='class 8', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_080_04': Element(name='class 8', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_080_05': Element(name='class 8', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_080_06': Element(name='class 8', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_080_07': Element(name='class 8', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_080_08': Element(name='class 8', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_080_09': Element(name='class 8', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_080_10': Element(name='class 8', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_100_01': Element(name='class 8', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_100_02': Element(name='class 8', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_100_03': Element(name='class 8', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_100_04': Element(name='class 8', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_100_05': Element(name='class 8', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_100_06': Element(name='class 8', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_100_07': Element(name='class 8', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_100_08': Element(name='class 8', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_100_09': Element(name='class 8', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl08_100_10': Element(name='class 8', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_020_01': Element(name='class 9', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_020_02': Element(name='class 9', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_020_03': Element(name='class 9', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_020_04': Element(name='class 9', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_020_05': Element(name='class 9', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_020_06': Element(name='class 9', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_020_07': Element(name='class 9', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_020_08': Element(name='class 9', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_020_09': Element(name='class 9', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_020_10': Element(name='class 9', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_040_01': Element(name='class 9', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_040_02': Element(name='class 9', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_040_03': Element(name='class 9', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_040_04': Element(name='class 9', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_040_05': Element(name='class 9', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_040_06': Element(name='class 9', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_040_07': Element(name='class 9', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_040_08': Element(name='class 9', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_040_09': Element(name='class 9', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_040_10': Element(name='class 9', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_060_01': Element(name='class 9', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_060_02': Element(name='class 9', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_060_03': Element(name='class 9', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_060_04': Element(name='class 9', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_060_05': Element(name='class 9', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_060_06': Element(name='class 9', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_060_07': Element(name='class 9', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_060_08': Element(name='class 9', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_060_09': Element(name='class 9', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_060_10': Element(name='class 9', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_080_01': Element(name='class 9', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_080_02': Element(name='class 9', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_080_03': Element(name='class 9', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_080_04': Element(name='class 9', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_080_05': Element(name='class 9', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_080_06': Element(name='class 9', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_080_07': Element(name='class 9', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_080_08': Element(name='class 9', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_080_09': Element(name='class 9', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_080_10': Element(name='class 9', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_100_01': Element(name='class 9', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_100_02': Element(name='class 9', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_100_03': Element(name='class 9', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_100_04': Element(name='class 9', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_100_05': Element(name='class 9', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_100_06': Element(name='class 9', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_100_07': Element(name='class 9', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_100_08': Element(name='class 9', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_100_09': Element(name='class 9', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl09_100_10': Element(name='class 9', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_020_01': Element(name='class 10', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_020_02': Element(name='class 10', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_020_03': Element(name='class 10', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_020_04': Element(name='class 10', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_020_05': Element(name='class 10', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_020_06': Element(name='class 10', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_020_07': Element(name='class 10', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_020_08': Element(name='class 10', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_020_09': Element(name='class 10', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_020_10': Element(name='class 10', name_suffix='20', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_040_01': Element(name='class 10', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_040_02': Element(name='class 10', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_040_03': Element(name='class 10', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_040_04': Element(name='class 10', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_040_05': Element(name='class 10', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_040_06': Element(name='class 10', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_040_07': Element(name='class 10', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_040_08': Element(name='class 10', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_040_09': Element(name='class 10', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_040_10': Element(name='class 10', name_suffix='40', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_060_01': Element(name='class 10', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_060_02': Element(name='class 10', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_060_03': Element(name='class 10', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_060_04': Element(name='class 10', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_060_05': Element(name='class 10', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_060_06': Element(name='class 10', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_060_07': Element(name='class 10', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_060_08': Element(name='class 10', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_060_09': Element(name='class 10', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_060_10': Element(name='class 10', name_suffix='60', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_080_01': Element(name='class 10', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_080_02': Element(name='class 10', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_080_03': Element(name='class 10', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_080_04': Element(name='class 10', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_080_05': Element(name='class 10', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_080_06': Element(name='class 10', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_080_07': Element(name='class 10', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_080_08': Element(name='class 10', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_080_09': Element(name='class 10', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_080_10': Element(name='class 10', name_suffix='80', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_100_01': Element(name='class 10', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_100_02': Element(name='class 10', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_100_03': Element(name='class 10', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_100_04': Element(name='class 10', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_100_05': Element(name='class 10', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_100_06': Element(name='class 10', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_100_07': Element(name='class 10', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_100_08': Element(name='class 10', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_100_09': Element(name='class 10', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'cl10_100_10': Element(name='class 10', name_suffix='100', reference='BW1987TDFBPA,MV1998ESOTTDFBPP'), 'asqas03': Element(name='asqas', name_suffix='', reference='vdBBMSB2016ASIASSTFI'), 'asqas08': Element(name='asqas', name_suffix='', reference='vdBBMSB2016ASIASSTFI'), 'asqas20': Element(name='asqas', name_suffix='', reference='vdBBMSB2016ASIASSTFI'), 'asqas34': Element(name='asqas', name_suffix='', reference='vdBBMSB2016ASIASSTFI')})

a mapping of instances to instance groups

moptipyapps.binpacking2d.bks.INST_GROUP_SORT_KEY(value, start=0, stop=9223372036854775807, /)

A sort key function for instance groups

moptipyapps.binpacking2d.bks.MXGA: Final[Element] = Element(name='MXGA', name_suffix='', reference='L2008AGAFTDBPP')

the MXGA algorithm for 2D bin packing with rotation

moptipyapps.binpacking2d.bks.NO_REF: Final[str] = '{{NO_REF}}'

a constant for no reference

moptipyapps.binpacking2d.bks.PAC: Final[Element] = Element(name='PAC', name_suffix='', reference='CGRS2020PACATSMTOOSFT2BPP')

the price-and-cut algorithm for 2D bin packing with rotation

Get the related work of a given type.

Parameters:
  • with_rotation (bool | None, default: None) – include the data with rotation

  • without_rotation (bool | None, default: None) – include the data without rotation

  • algo_select (Callable[[Element], bool], default: <function <lambda> at 0x7f2d913daa20>) – the algorithm selector

  • inst_group_select (Callable[[Element], bool], default: <function <lambda> at 0x7f2d913daac0>) – the instance group selector

Return type:

tuple[tuple[bool, Element, Element, int], ...]

Returns:

An iterable with the related works

moptipyapps.binpacking2d.bks.make_comparison_table(dest, data, name_to_strs=<function <lambda>>, format_best=<function <lambda>>, count_best_over=())[source]

Make a comparison table in LaTeX.

Parameters:
Return type:

None

moptipyapps.binpacking2d.bks.make_comparison_table_data(data, with_rotation, algo_from_pr=<function <lambda>>, algo_sort_key=<function <lambda>>, rw_algo_selector=<function <lambda>>, aggregator=<function mean>, additional=<function <lambda>>)[source]

Create the data for an end result comparison table.

Parameters:
Return type:

tuple[tuple[Element, ...], tuple[tuple[Element, tuple[int | float | None, ...]], ...]]

Returns:

the table data: the title row columns followed by the data row-by-row, each row leading with an instance group identifier

moptipyapps.binpacking2d.experiment module

An example experiment for bin packing.

moptipyapps.binpacking2d.experiment.MAX_FES: Final[int] = 1000000

the maximum number of FEs

moptipyapps.binpacking2d.experiment.base_setup(instance, encoding, objective)[source]

Create the basic setup.

Parameters:
Return type:

tuple[SignedPermutations, Execution]

Returns:

the search space and the basic execution

moptipyapps.binpacking2d.experiment.fea(instance, encoding, objective)[source]

Create the FEA setup.

Parameters:
Return type:

Execution

Returns:

the RLS execution

moptipyapps.binpacking2d.experiment.rls(instance, encoding, objective)[source]

Create the RLS setup.

Parameters:
Return type:

Execution

Returns:

the RLS execution

moptipyapps.binpacking2d.experiment.run(base_dir, n_runs=23)[source]

Run the experiment.

Parameters:
  • base_dir (str) – the base directory

  • n_runs (int, default: 23) – the number of runs, by default 23

Return type:

None

moptipyapps.binpacking2d.instance module

A Two-Dimensional Bin Packing instance.

This module provides an instance of the two-dimensional bin packing problem as defined in 2DPackLib [1, 2] as well as the four non-trivial ‘Almost Squares in Almost Squares’ instances [6, 7].

All instances of Instance are two-dimensional numpy ndarrays with additional attributes. Each instance has a name. Instances also specify a bin_width and bin_height. They define the number n_different_items of items with different IDs. Notice that in the 2DPackLib dataset, a benchmark instance may contain the same item multiple times. Obviously, all items of the same ID have the exact same width and height, meaning that we only need to store them once and remember how often they occur. (Notice that the opposite is not true, i.e., not all items with the same width and height do have the same ID.) Anyway, the total number n_items of items, i.e., the sum of all the repetitions of all items, is also stored.

The matrix data of the instance class is laid out as follows: There is one row for each item. The row contains the width of the item, the height of the item, and the number of times the item will occur. The row at index i stands for the item with ID i+1.

Instances can be loaded directly from a 2DPackLib file via Instance.from_2dpacklib(). They can also be loaded from a compact string representation (via Instance.from_compact_str()) and can also be converted to such a compact representation (via Instance.to_compact_str()). This library ships with a set of pre-defined instances as resource which can be obtained via Instance.from_resource() and listed via Instance.list_resources().

We provide the instances of the sets A [3], BENG [4], and CLASS [5] from 2DPackLib. Additionally, we include the four non-trivial ‘Almost Squares in Almost Squares’ instances [6,7].

Initial work on this code has been contributed by Mr. Rui ZHAO (赵睿), <zr1329142665@163.com> a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

  1. Manuel Iori, Vinícius Loti de Lima, Silvano Martello, and Michele Monaci. 2DPackLib. https://site.unibo.it/operations-research/en/research/2dpacklib

  2. Manuel Iori, Vinícius Loti de Lima, Silvano Martello, and Michele Monaci. 2DPackLib: A Two-Dimensional Cutting and Packing Library. Optimization Letters 16(2):471-480. March 2022. https://doi.org/10.1007/s11590-021-01808-y

  3. Rita Macedo, Cláudio Alves, and José M. Valério de Carvalho. Arc-Flow Model for the Two-Dimensional Guillotine Cutting Stock Problem. Computers & Operations Research 37(6):991-1001. June 2010. https://doi.org/10.1016/j.cor.2009.08.005.

  4. Bengt-Erik Bengtsson. Packing Rectangular Pieces - A Heuristic Approach. The Computer Journal 25(3):353-357, August 1982. https://doi.org/10.1093/comjnl/25.3.353

  5. J.O. Berkey and P.Y. Wang. Two Dimensional Finite Bin Packing Algorithms. Journal of the Operational Research Society 38(5):423-429. May 1987. https://doi.org/10.1057/jors.1987.70

  6. Daan van den Berg, Florian Braam, Mark Moes, Emiel Suilen, and Sandjai Bhulai. Almost Squares in Almost Squares: Solving the Final Instance. In The Fifth International Conference on Data Analytics (DATA ANALYTICS’16) October 9-13, 2016, Venice, Italy, pages 69-74. IARIA. ISBN: 9781612085104. https://hdl.handle.net/11245/1.545914. https://math.vu.nl/~sbhulai/publications/data_analytics2016b.pdf.

  7. H. Simonis and B. O’Sullivan. Almost Square Packing in 8th International Conference on Integration of AI and OR Techniques in Constraint Programming for Combinatorial Optimization Problems (CPAIOR’11), May 23-27, 2011, Berlin, Germany, pages 196-209. Berlin, Germany: Springer-Verlag. doi:10.1007/978-3-642-21311-3_19.

>>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]])
+>>> ins.name
+'a'
+>>> ins.bin_width
+100
+>>> ins.bin_height
+50
+>>> ins.dtype
+dtype('int8')
+>>> ins.n_different_items
+3
+>>> ins.to_compact_str()
+'a;3;100;50;10,5;3,3;5,5'
+>>> ins = Instance("b", 100, 50, np.array([[10, 5, 1], [3, 3, 1], [3, 3, 1]]))
+>>> ins.name
+'b'
+>>> ins.bin_width
+100
+>>> ins.bin_height
+50
+>>> ins.dtype
+dtype('int8')
+>>> ins.to_compact_str()
+'b;3;100;50;10,5;3,3;3,3'
+>>> ins = Instance.from_resource("cl02_020_06")
+>>> ins.dtype
+dtype('int8')
+>>> ins.n_different_items
+20
+>>> ins.n_items
+20
+>>> ins = Instance.from_resource("a25")
+>>> ins.dtype
+dtype('int16')
+>>> ins.n_different_items
+75
+>>> ins.n_items
+156
+
moptipyapps.binpacking2d.instance.BIN_HEIGHT: Final[str] = 'binHeight'

the bin height

moptipyapps.binpacking2d.instance.BIN_WIDTH: Final[str] = 'binWidth'

the bin width

moptipyapps.binpacking2d.instance.IDX_HEIGHT: Final[int] = 1

the index of the height element in an item of an instance

moptipyapps.binpacking2d.instance.IDX_REPETITION: Final[int] = 2

the index of the repetitions element in an item of an instance

moptipyapps.binpacking2d.instance.IDX_WIDTH: Final[int] = 0

the index of the width element in an item of an instance

moptipyapps.binpacking2d.instance.INSTANCES_RESOURCE: Final[str] = 'instances.txt'

the instances resource name

moptipyapps.binpacking2d.instance.INTERNAL_SEP: Final[str] = ','

The internal item separator

class moptipyapps.binpacking2d.instance.Instance(name: str, bin_width: int, bin_height: int, matrix: ndarray | list[list[int]])[source]

Bases: Component, ndarray

An instance of the 2D Bin Packing Problem.

Each row of the matrix contains three values: 1. the item’s width, 2. the item’s height, 3. how often the item occurs.

bin_height: int

the bin height

bin_width: int

the bin width

static from_2dpacklib(file)[source]

Load a problem instance from the 2dpacklib format.

Parameters:

file (str) – the file path

Return type:

Instance

Returns:

the instance

static from_compact_str(data)[source]

Transform a compact string back to an instance.

Parameters:

data (str) – the string data

Return type:

Instance

Returns:

the instance

>>> ins = Instance("x", 500, 50, [[3, 5, 1], [2, 5, 2]])
+>>> Instance.from_compact_str(ins.to_compact_str()).to_compact_str()
+'x;2;500;50;3,5;2,5,2'
+
static from_resource(name)[source]

Load an instance from a resource.

Parameters:

name (str) – the name string

Return type:

Instance

Returns:

the instance

>>> Instance.from_resource("a01").to_compact_str()
+'a01;2;2750;1220;463,386,18;1680,420,6'
+>>> Instance.from_resource("a07").to_compact_str()
+'a07;5;2750;1220;706,286,8;706,516,16;986,433,10;1120,486,12;1120,986,12'
+>>> Instance.from_resource("a08") is Instance.from_resource("a08")
+True
+>>> Instance.from_resource("a08") is Instance.from_resource("a09")
+False
+
get_standard_item_sequence()[source]

Get the standardized item sequence.

Return type:

list[int]

Returns:

the standardized item sequence

>>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]])
+>>> ins.get_standard_item_sequence()
+[1, 2, 3]
+>>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 2]])
+>>> ins.get_standard_item_sequence()
+[1, 2, 3, 3]
+>>> ins = Instance("a", 100, 50, [[10, 5, 2], [3, 3, 3], [5, 5, 4]])
+>>> ins.get_standard_item_sequence()
+[1, 1, 2, 2, 2, 3, 3, 3, 3]
+
static list_resources()[source]

Get a tuple of all the instances available as resource.

Return type:

tuple[str, ...]

Returns:

the tuple with the instance names

>>> len(list(Instance.list_resources()))
+557
+
static list_resources_groups()[source]

List the instance groups in the resources.

One problem of the benchmark set for 2-dimensional bin packing is that it has many instances:

>>> len(Instance.list_resources())
+557
+

With this function, we can group several of these instances together in a way that is compliant with literature in order to then compute statistics over these groups. Presenting data gathered over…

>>> len(Instance.list_resources_groups())
+56
+

…groups is much easier than dealing with over 500 instances.

Return type:

tuple[tuple[str, str | None, tuple[str, ...]], ...]

Returns:

the instance groups, in a two level hierarchy. The result is a sequence of tuples. Each tuple has the top-level group name and, optionally, a second-level group name (or None if no second level group exists). The third tuple element is a sequence of instance names.

>>> [(v[0], v[1], len(v[2])) for v in
+...     Instance.list_resources_groups()]
+[('a', 'small', 14), ('a', 'med', 14), ('a', 'large', 15), ('beng', '1-8', 8), ('beng', '9-10', 2), ('class 1', '20', 10), ('class 1', '40', 10), ('class 1', '60', 10), ('class 1', '80', 10), ('class 1', '100', 10), ('class 2', '20', 10), ('class 2', '40', 10), ('class 2', '60', 10), ('class 2', '80', 10), ('class 2', '100', 10), ('class 3', '20', 10), ('class 3', '40', 10), ('class 3', '60', 10), ('class 3', '80', 10), ('class 3', '100', 10), ('class 4', '20', 10), ('class 4', '40', 10), ('class 4', '60', 10), ('class 4', '80', 10), ('class 4', '100', 10), ('class 5', '20', 10), ('class 5', '40', 10), ('class 5', '60', 10), ('class 5', '80', 10), ('class 5', '100', 10), ('class 6', '20', 10), ('class 6', '40', 10), ('class 6', '60', 10), ('class 6', '80', 10), ('class 6', '100', 10), ('class 7', '20', 10), ('class 7', '40', 10), ('class 7', '60', 10), ('class 7', '80', 10), ('class 7', '100', 10), ('class 8', '20', 10), ('class 8', '40', 10), ('class 8', '60', 10), ('class 8', '80', 10), ('class 8', '100', 10), ('class 9', '20', 10), ('class 9', '40', 10), ('class 9', '60', 10), ('class 9', '80', 10), ('class 9', '100', 10), ('class 10', '20', 10), ('class 10', '40', 10), ('class 10', '60', 10), ('class 10', '80', 10), ('class 10', '100', 10), ('asqas', None, 4)]
+
log_parameters_to(logger)[source]

Log the parameters describing this bin packing instance to the logger.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

>>> from moptipy.utils.logger import InMemoryLogger
+>>> with InMemoryLogger() as l:
+...     with l.key_values("I") as kv:
+...         Instance.from_resource("beng05").log_parameters_to(kv)
+...     print(repr('@'.join(l.get_log())))
+'BEGIN_I@name: beng05@class: moptipyapps.binpacking2d.instance.Instance@nItems: 100@nDifferentItems: 100@binWidth: 25@binHeight: 10@dtype: b@END_I'
+
lower_bound_bins: int

the minimum number of bins that this instance requires

n_different_items: int

the number of different items

n_items: int

the total number of items (including repetitions)

name: str

the name of the instance

to_compact_str()[source]

Convert the instance to a compact string.

The format of the string is a single line of semi-colon separated values. The values are: name;n_items;bin_width;bin_height, followed by the sequence of items, each in the form of ;width,heigh[,times], where times is optional and only added if the item occurs more than once.

Return type:

str

Returns:

a single line string with all the instance data

>>> ins = Instance("x", 500, 50, [[3, 5, 1], [2, 5, 2]])
+>>> ins.to_compact_str()
+'x;2;500;50;3,5;2,5,2'
+>>> ins.n_different_items
+2
+
total_item_area: int

the total area occupied by all items

moptipyapps.binpacking2d.instance.N_DIFFERENT_ITEMS: Final[str] = 'nDifferentItems'

the number of different items.

moptipyapps.binpacking2d.instance.N_ITEMS: Final[str] = 'nItems'

the total number of items

moptipyapps.binpacking2d.make_instances module

Obtain Instances of the 2D-dimensional Bin Packing Problem.

With this program, we can obtain the instances of the two-dimensional bin packing problem and convert them to a singular resource file. The resource file holds one instance per line.

  1. Manuel Iori, Vinícius Loti de Lima, Silvano Martello, and Michele Monaci. 2DPackLib. https://site.unibo.it/operations-research/en/research/2dpacklib

  2. Manuel Iori, Vinícius Loti de Lima, Silvano Martello, and Michele Monaci. 2DPackLib: A Two-Dimensional Cutting and Packing Library. Optimization Letters 16(2):471-480. March 2022. https://doi.org/10.1007/s11590-021-01808-y

moptipyapps.binpacking2d.make_instances.append_almost_squares_strings(collector)[source]

Append the strings of the almost squares instances.

Parameters:

collector (Callable[[tuple[str, str]], Any]) – the instance collector

Return type:

None

Returns:

the strings

moptipyapps.binpacking2d.make_instances.download_2dpacklib_instances(dest_dir, source_urls=('https://site.unibo.it/operations-research/en/research/2dpacklib/a.zip', 'https://site.unibo.it/operations-research/en/research/2dpacklib/beng.zip', 'https://site.unibo.it/operations-research/en/research/2dpacklib/class.zip'), http=<urllib3.poolmanager.PoolManager object>)[source]

Download the instances from 2DPackLib to a folder.

This function downloads the instances from 2DPackLib, which are provided as zip archives containing one file per instance. It will extract all the instances into the folder dest_dir and return a tuple of the extracted files. You can specify the source URLs of the zip archives if you want, but by default we use the three instance sets A, BENG, and CLASS.

Parameters:
  • dest_dir (str) – the destination directory

  • source_urls (Iterable[str], default: ('https://site.unibo.it/operations-research/en/research/2dpacklib/a.zip', 'https://site.unibo.it/operations-research/en/research/2dpacklib/beng.zip', 'https://site.unibo.it/operations-research/en/research/2dpacklib/class.zip')) – the source URLs from which to download the zip archives with the 2DPackLib-formatted instances

  • http (PoolManager, default: <urllib3.poolmanager.PoolManager object at 0x7f2d90720770>) – the HTTP pool

Return type:

Iterable[Path]

Returns:

the list of unpackaged files

moptipyapps.binpacking2d.make_instances.join_instances_to_compact(binpacklib2d_files, dest_file, normalizer=<function __normalize_2dpacklib_inst_name>)[source]

Join all instances from a set of 2DPackLib files to one compact file.

Parameters:
  • binpacklib2d_files (Iterable[str]) – the iterable of 2DPackLib file paths

  • dest_file (str) – the destination file

  • normalizer (Callable[[str], str], default: <function __normalize_2dpacklib_inst_name at 0x7f2d911a4900>) – the name normalizer, i.e., a function that processes and/or transforms an instance name

Return type:

tuple[Path, Iterable[str]]

Returns:

the canonical destination path and the list of instance names stored

moptipyapps.binpacking2d.make_instances.make_2dpacklib_resource(dest_file=None, source_urls=('https://site.unibo.it/operations-research/en/research/2dpacklib/a.zip', 'https://site.unibo.it/operations-research/en/research/2dpacklib/beng.zip', 'https://site.unibo.it/operations-research/en/research/2dpacklib/class.zip'), normalizer=<function __normalize_2dpacklib_inst_name>)[source]

Make the resource with all the relevant 2DPackLib instances.

Parameters:
  • dest_file (str | None, default: None) – the optional path to the destination file

  • source_urls (Iterable[str], default: ('https://site.unibo.it/operations-research/en/research/2dpacklib/a.zip', 'https://site.unibo.it/operations-research/en/research/2dpacklib/beng.zip', 'https://site.unibo.it/operations-research/en/research/2dpacklib/class.zip')) – the source URLs from which to download the zip archives with the 2DPackLib-formatted instances

  • normalizer (Callable[[str], str], default: <function __normalize_2dpacklib_inst_name at 0x7f2d911a4900>) – the name normalizer, i.e., a function that processes and/or transforms an instance name

Return type:

tuple[Path, Iterable[str]]

Returns:

the canonical path to the and the list of instance names stored

moptipyapps.binpacking2d.packing module

A two-dimensional packing.

moptipyapps.binpacking2d.packing.IDX_BIN: Final[int] = 1

the index of the bin in a Packing row

moptipyapps.binpacking2d.packing.IDX_BOTTOM_Y: Final[int] = 3

the index of the bottom y coordinate in a Packing row

moptipyapps.binpacking2d.packing.IDX_ID: Final[int] = 0

the index of the ID in a Packing row

moptipyapps.binpacking2d.packing.IDX_LEFT_X: Final[int] = 2

the index of the left x coordinate in a Packing row

moptipyapps.binpacking2d.packing.IDX_RIGHT_X: Final[int] = 4

the index of the right x coordinate in a Packing row

moptipyapps.binpacking2d.packing.IDX_TOP_Y: Final[int] = 5

the index of the top y coordinate in a Packing row

class moptipyapps.binpacking2d.packing.Packing(instance: Instance)[source]

Bases: Component, ndarray

A packing, i.e., a solution to an 2D bin packing instance.

A packing is a two-dimensional numpy array. In each row, the position of one item is stored: 1. the item ID (starts at 1), 2. the bin into which the item is packaged (starts at 1), 3. the left x coordinate, 4. the bottom y coordinate, 5. the right x coordinate, 6. the top y coordinate.

static from_log(file, instance=None)[source]

Load a packing from a log file.

Parameters:
  • file (str) – the log file path

  • instance (Instance | None, default: None) – the optional Packing instance: if None is provided, we try to load it from the resources

Return type:

Packing

Returns:

the Packing

instance: Instance

the 2d bin packing instance

n_bins: int

the number of bins

moptipyapps.binpacking2d.packing_result module

An extended end result record to represent packings.

This class extends the information provided by end_results. It allows us to compare the results of experiments over different objective functions. It also represents the bounds for the number of bins and for the objective functions. It also includes the problem-specific information of two-dimensional bin packing instances.

class moptipyapps.binpacking2d.packing_result.CsvReader(columns)[source]

Bases: object

A class for CSV reading of PackingResult instances.

parse_row(data)[source]

Parse a row of data.

Parameters:

data (list[str]) – the data row

Return type:

PackingResult

Returns:

the end result statistics

class moptipyapps.binpacking2d.packing_result.CsvWriter(scope=None)[source]

Bases: object

A class for CSV writing of PackingResult.

get_column_titles()[source]

Get the column titles.

Return type:

Iterable[str]

Returns:

the column titles

Get the bottom footer comments.

Parameters:

dest – the destination

Return type:

Iterable[str]

Get any possible footer comments.

Return type:

Iterable[str]

Returns:

the footer comments

get_header_comments()[source]

Get any possible header comments.

Return type:

Iterable[str]

Returns:

the header comments

get_row(data)[source]

Render a single packing result record to a CSV row.

Parameters:

data (PackingResult) – the end result record

Return type:

Iterable[str]

Returns:

the iterable with the row data

scope: Final[str | None]

an optional scope

setup(data)[source]

Set up this csv writer based on existing data.

Parameters:

data (Iterable[PackingResult]) – the data to setup with

Return type:

CsvWriter

Returns:

this writer

moptipyapps.binpacking2d.packing_result.DEFAULT_OBJECTIVES: Final[tuple[Callable[[Instance], Objective], ...]] = (<class 'moptipyapps.binpacking2d.objectives.bin_count.BinCount'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.BinCountAndLastSkyline'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_empty.BinCountAndEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_small.BinCountAndSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.BinCountAndLowestSkyline'>)

the default objective functions

moptipyapps.binpacking2d.packing_result.KEY_BIN_HEIGHT: Final[str] = 'binHeight'

the bin height

moptipyapps.binpacking2d.packing_result.KEY_BIN_WIDTH: Final[str] = 'binWidth'

the bin width

moptipyapps.binpacking2d.packing_result.KEY_N_DIFFERENT_ITEMS: Final[str] = 'nDifferentItems'

the number of different items

moptipyapps.binpacking2d.packing_result.KEY_N_ITEMS: Final[str] = 'nItems'

the number of items

moptipyapps.binpacking2d.packing_result.LOWER_BOUNDS_BIN_COUNT: Final[str] = 'bins.lowerBound'

the start string for bin bounds

class moptipyapps.binpacking2d.packing_result.PackingResult(end_result, n_items, n_different_items, bin_width, bin_height, objectives, objective_bounds, bin_bounds)[source]

Bases: EvaluationDataElement

An end result record of one run of one packing algorithm on one problem.

This record provides the information of the outcome of one application of one algorithm to one problem instance in an immutable way.

bin_bounds: Mapping[str, int]

the bounds for the minimum number of bins of the instance

bin_height: int

the bin height

bin_width: int

the bin width

end_result: EndResult

the original end result record

n_different_items: int

the number of different items in the instance

n_items: int

the number of items in the instance

objective_bounds: Mapping[str, int | float]

the bounds for the objective values (append “.lowerBound” and “.upperBound” to all objective function names)

objectives: Mapping[str, int | float]

the objective values evaluated after the optimization

moptipyapps.binpacking2d.packing_result.from_csv(file)[source]

Load the packing results from a CSV file.

Parameters:

file (str) – the file to read from

Return type:

Iterable[PackingResult]

Returns:

the iterable with the packing result

moptipyapps.binpacking2d.packing_result.from_logs(directory, consumer, objectives=(<class 'moptipyapps.binpacking2d.objectives.bin_count.BinCount'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.BinCountAndLastSkyline'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_empty.BinCountAndEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_small.BinCountAndSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.BinCountAndLowestSkyline'>), bin_bounds=mappingproxy({'bins.lowerBound': <function <lambda>>, 'bins.lowerBound.geometric': <function __lb_geometric>, 'bins.lowerBound.damv': <function <lambda>>}))[source]

Parse a directory recursively to get all packing results.

Parameters:
  • directory (str) – the directory to parse

  • consumer (Callable[[PackingResult], None]) – the consumer for receiving the results

  • objectives (Iterable[Callable[[Instance], Objective]], default: (<class 'moptipyapps.binpacking2d.objectives.bin_count.BinCount'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.BinCountAndLastSkyline'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_empty.BinCountAndEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_small.BinCountAndSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.BinCountAndLowestSkyline'>)) – the objective function factories

  • bin_bounds (Mapping[str, Callable[[Instance], int]], default: mappingproxy({'bins.lowerBound': <function <lambda> at 0x7f2d9174d580>, 'bins.lowerBound.geometric': <function __lb_geometric at 0x7f2d913656c0>, 'bins.lowerBound.damv': <function <lambda> at 0x7f2d91722b60>})) – the bin bounds calculators

Return type:

None

moptipyapps.binpacking2d.packing_result.from_packing_and_end_result(end_result, packing, objectives=(<class 'moptipyapps.binpacking2d.objectives.bin_count.BinCount'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.BinCountAndLastSkyline'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_empty.BinCountAndEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_small.BinCountAndSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.BinCountAndLowestSkyline'>), bin_bounds=mappingproxy({'bins.lowerBound': <function <lambda>>, 'bins.lowerBound.geometric': <function __lb_geometric>, 'bins.lowerBound.damv': <function <lambda>>}), cache=None)[source]

Create a PackingResult from an EndResult and a Packing.

Parameters:
  • end_result (EndResult) – the end results record

  • packing (Packing) – the packing

  • bin_bounds (Mapping[str, Callable[[Instance], int]], default: mappingproxy({'bins.lowerBound': <function <lambda> at 0x7f2d9174d580>, 'bins.lowerBound.geometric': <function __lb_geometric at 0x7f2d913656c0>, 'bins.lowerBound.damv': <function <lambda> at 0x7f2d91722b60>})) – the bounds computing functions

  • objectives (Iterable[Callable[[Instance], Objective]], default: (<class 'moptipyapps.binpacking2d.objectives.bin_count.BinCount'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.BinCountAndLastSkyline'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_empty.BinCountAndEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_small.BinCountAndSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.BinCountAndLowestSkyline'>)) – the objective function factories

  • cache (Optional[Mapping[str, tuple[Mapping[str, int], tuple[Objective, ...], Mapping[str, int | float]]]], default: None) – a cache that can store stuff if this function is to be called repeatedly

Return type:

PackingResult

Returns:

the packing result

moptipyapps.binpacking2d.packing_result.from_single_log(file, objectives=(<class 'moptipyapps.binpacking2d.objectives.bin_count.BinCount'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.BinCountAndLastSkyline'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_empty.BinCountAndEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_small.BinCountAndSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.BinCountAndLowestSkyline'>), bin_bounds=mappingproxy({'bins.lowerBound': <function <lambda>>, 'bins.lowerBound.geometric': <function __lb_geometric>, 'bins.lowerBound.damv': <function <lambda>>}), cache=None)[source]

Create a PackingResult from a file.

Parameters:
  • file (str) – the file path

  • objectives (Iterable[Callable[[Instance], Objective]], default: (<class 'moptipyapps.binpacking2d.objectives.bin_count.BinCount'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.BinCountAndLastSkyline'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_empty.BinCountAndEmpty'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_small.BinCountAndSmall'>, <class 'moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.BinCountAndLowestSkyline'>)) – the objective function factories

  • bin_bounds (Mapping[str, Callable[[Instance], int]], default: mappingproxy({'bins.lowerBound': <function <lambda> at 0x7f2d9174d580>, 'bins.lowerBound.geometric': <function __lb_geometric at 0x7f2d913656c0>, 'bins.lowerBound.damv': <function <lambda> at 0x7f2d91722b60>})) – the bounds computing functions

  • cache (Optional[Mapping[str, tuple[Mapping[str, int], tuple[Objective, ...], Mapping[str, int | float]]]], default: None) – a cache that can store stuff if this function is to be called repeatedly

Return type:

PackingResult

Returns:

the packing result

moptipyapps.binpacking2d.packing_result.to_csv(results, file)[source]

Write a sequence of packing results to a file in CSV format.

Parameters:
Return type:

Path

Returns:

the path of the file that was written

moptipyapps.binpacking2d.packing_space module

Here we provide a Space of bin packings.

The bin packing object is defined in module packing. Basically, it is a two-dimensional numpy array holding, for each item, its ID, its bin ID, as well as its location defined by four coordinates.

  1. Dequan Liu and Hongfei Teng. An Improved BL-Algorithm for Genetic Algorithm of the Orthogonal Packing of Rectangles. European Journal of Operational Research. 112(2):413-420. January (1999). https://doi.org/10.1016/S0377-2217(97)00437-2. http://www.paper.edu.cn/scholar/showpdf/MUT2AN0IOTD0Mxxh.

class moptipyapps.binpacking2d.packing_space.PackingSpace(instance)[source]

Bases: Space

An implementation of the Space API of for 2D bin packings charts.

copy(dest, source)[source]

Copy one packing to another one.

Parameters:
  • dest (Packing) – the destination packing

  • source (Packing) – the source packing

Return type:

None

create()[source]

Create a packing object without assigning items to locations.

Return type:

Packing

Returns:

the (empty, uninitialized) packing object

>>> inst = Instance.from_resource("a01")
+>>> space = PackingSpace(inst)
+>>> x = space.create()
+>>> x.shape
+(24, 6)
+>>> x.instance is inst
+True
+>>> type(x)
+<class 'moptipyapps.binpacking2d.packing.Packing'>
+
from_str(text)[source]

Convert a string to a packing.

Parameters:

text (str) – the string

Return type:

Packing

Returns:

the packing

>>> inst = Instance.from_resource("a01")
+>>> space = PackingSpace(inst)
+>>> y1 = space.create()
+>>> xx = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+...                1, 2, 2, 2, 2, 2, 2])
+>>> import moptipyapps.binpacking2d.encodings.ibl_encoding_1 as e
+>>> y1.n_bins = e._decode(
+...     xx, y1, inst, inst.bin_width, inst.bin_height)
+>>> y2 = space.from_str(space.to_str(y1))
+>>> space.is_equal(y1, y2)
+True
+>>> y1 is y2
+False
+
instance: Final[Instance]

The instance to which the packings apply.

is_equal(x1, x2)[source]

Check if two bin packings have the same contents.

Parameters:
Return type:

bool

Returns:

True if both packings are for the same instance and have the same structure

>>> inst = Instance.from_resource("a01")
+>>> space = PackingSpace(inst)
+>>> y1 = space.create()
+>>> y1.fill(0)
+>>> y2 = space.create()
+>>> y2.fill(0)
+>>> space.is_equal(y1, y2)
+True
+>>> y1 is y2
+False
+>>> y1[0, 0] = 1
+>>> space.is_equal(y1, y2)
+False
+
log_parameters_to(logger)[source]

Log the parameters of the space to the given logger.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

n_points()[source]

Get the number of possible packings.

If we indeed consider that any object could be at any place, then there would be an incomprehensibly large number of possible packings. Here, we consider the bottom-left condition and the idea of encoding packings as signed permutations, as in the Liu and Teng paper “An Improved BL-Algorithm for Genetic Algorithm of the Orthogonal Packing of Rectangles.” In this case, if n items are to be packed, the number of possible packings won’t exceed 2^n * n!.

Return type:

int

Returns:

the approximate number of packings

>>> inst = Instance.from_resource("a01")
+>>> inst.n_items
+24
+>>> space = PackingSpace(inst)
+>>> space.n_points()
+10409396852733332453861621760000
+>>> from math import factorial
+>>> (2 ** 24) * factorial(24)
+10409396852733332453861621760000
+
to_str(x)[source]

Convert a packing to a string.

Parameters:

x (Packing) – the packing

Return type:

str

Returns:

a string corresponding to the flattened packing array

>>> inst = Instance.from_resource("a01")
+>>> space = PackingSpace(inst)
+>>> y = space.create()
+>>> xx = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+...                1, 2, 2, 2, 2, 2, 2])
+>>> import moptipyapps.binpacking2d.encodings.ibl_encoding_1 as e
+>>> e._decode(xx, y, inst, inst.bin_width, inst.bin_height)
+5
+>>> space.to_str(y)
+'1;1;0;0;463;386;1;1;463;0;926;386;1;1;926;0;1389;386;1;1;1389;0;1852;386;1;1;1852;0;2315;386;1;1;0;386;463;772;1;1;463;386;926;772;1;1;926;386;1389;772;1;1;1389;386;1852;772;1;1;1852;386;2315;772;1;1;0;772;463;1158;1;1;463;772;926;1158;1;1;926;772;1389;1158;1;1;1389;772;1852;1158;1;1;1852;772;2315;1158;1;2;0;0;463;386;1;2;463;0;926;386;1;2;926;0;1389;386;2;2;0;386;1680;806;2;3;0;0;1680;420;2;3;0;420;1680;840;2;4;0;0;1680;420;2;4;0;420;1680;840;2;5;0;0;1680;420'
+
validate(x)[source]

Check if a packing is valid and feasible.

This method performs a comprehensive feasibility and sanity check of a packing. It ensures that the packing could be implemented in the real world exactly as it is given here and that all data are valid and that it matches to the bin packing instance. This includes:

  • checking that all data types, numpy dtypes, and matrix shapes are correct

  • checking that the packing belongs to the same instance as this space

  • checking that no objects in the same bin overlap

  • checking that all objects occur exactly as often as prescribed by the instance

  • checking that no object extends outside of its bin

  • checking that all objects have the same width/height as prescribed in the instance (with possible rotations)

  • check that bin ids are assigned starting at 1 and incrementing in steps of 1

Parameters:

x (Packing) – the packing

Raises:
  • TypeError – if any component of the packing is of the wrong type

  • ValueError – if the packing is not feasible

Return type:

None

moptipyapps.binpacking2d.packing_statistics module

An extended end result statistics record to represent packings.

class moptipyapps.binpacking2d.packing_statistics.CsvReader(columns)[source]

Bases: object

A class for CSV parsing to get PackingStatistics.

parse_row(data)[source]

Parse a row of data.

Parameters:

data (list[str]) – the data row

Return type:

PackingStatistics

Returns:

the end result statistics

class moptipyapps.binpacking2d.packing_statistics.CsvWriter(scope=None)[source]

Bases: object

A class for CSV writing of PackingStatistics.

get_column_titles()[source]

Get the column titles.

Parameters:

dest – the destination string consumer

Return type:

Iterable[str]

Get the bottom footer comments.

Parameters:

dest – the destination

Return type:

Iterable[str]

Get any possible footer comments.

Return type:

Iterable[str]

Returns:

the footer comments

get_header_comments()[source]

Get any possible header comments.

Return type:

Iterable[str]

Returns:

the header comments

get_row(data)[source]

Render a single packing result record to a CSV row.

Parameters:

data (PackingStatistics) – the end result record

Return type:

Iterable[str]

Returns:

the iterable with the row text

scope: Final[str | None]

an optional scope

setup(data)[source]

Set up this csv writer based on existing data.

Parameters:

data (Iterable[PackingStatistics]) – the data to setup with

Return type:

CsvWriter

Returns:

this writer

class moptipyapps.binpacking2d.packing_statistics.PackingStatistics(end_statistics, n_items, n_different_items, bin_width, bin_height, objectives, objective_bounds, bin_bounds)[source]

Bases: EvaluationDataElement

An end statistics record of one run of one algorithm on one problem.

This record provides the information of the outcome of one application of one algorithm to one problem instance in an immutable way.

bin_bounds: Mapping[str, int]

the bounds for the minimum number of bins of the instance

bin_height: int

the bin height

bin_width: int

the bin width

end_statistics: EndStatistics

the original end statistics record

n_different_items: int

the number of different items in the instance

n_items: int

the number of items in the instance

objective_bounds: Mapping[str, int | float]

the bounds for the objective values (append “.lowerBound” and “.upperBound” to all objective function names)

objectives: Mapping[str, SampleStatistics]

the objective values evaluated after the optimization

moptipyapps.binpacking2d.packing_statistics.from_csv(file)[source]

Load the packing statistics from a CSV file.

Parameters:

file (str) – the file to read from

Return type:

Iterable[PackingStatistics]

Returns:

the iterable with the packing statistics

moptipyapps.binpacking2d.packing_statistics.from_packing_results(results, consumer)[source]

Create packing statistics from a sequence of packing results.

Parameters:
Return type:

None

moptipyapps.binpacking2d.packing_statistics.to_csv(results, file)[source]

Write a sequence of packing statistics to a file in CSV format.

Parameters:
Return type:

Path

Returns:

the path of the file that was written

moptipyapps.binpacking2d.plot_packing module

Plot a packing into one figure.

moptipyapps.binpacking2d.plot_packing.default_packing_item_str(item_id, item_index, item_in_bin_index)[source]

Get a packing item string(s).

The default idea is to include the item id, the index of the item in the bin, and the overall index of the item. If the space is insufficient, we remove the latter or the latter two. Hence, this function returns a tuple of three strings.

Parameters:
  • item_id (int) – the ID of the packing item

  • item_index (int) – the item index

  • item_in_bin_index (int) – the index of the item in its bin

Return type:

Iterable[str]

Returns:

the string

moptipyapps.binpacking2d.plot_packing.plot_packing(packing, max_rows=3, max_bins_per_row=3, default_width_per_bin=8.6, max_width=8.6, default_height_per_bin=5.315092303249095, max_height=9, packing_item_str=<function default_packing_item_str>, importance_to_font_size_func=<function importance_to_font_size>, dpi=384.0)[source]

Plot a packing.

Each item is drawn in a different color. Each item rectangle includes, if there is enough space, the item-ID. If there is more space, also the index of the item inside the bin (starting at 1) is included. If there is yet more space, even the overall index of the item is included.

Parameters:
  • packing (Packing | str) – the packing or the file to load it from

  • max_rows (int, default: 3) – the maximum number of rows

  • max_bins_per_row (int, default: 3) – the maximum number of bins per row

  • default_width_per_bin (float | int | None, default: 8.6) – the optional default width of a column

  • max_height (float | int | None, default: 9) – the maximum height

  • default_height_per_bin (float | int | None, default: 5.315092303249095) – the optional default height per row

  • max_width (float | int | None, default: 8.6) – the maximum width

  • packing_item_str (Callable[[int, int, int], Union[str, Iterable[str]]], default: <function default_packing_item_str at 0x7f2d9126f2e0>) – the function converting an item id, item index, and item-in-bin index to a string or sequence of strings (of decreasing length)

  • importance_to_font_size_func (Callable[[int], float], default: <function importance_to_font_size at 0x7f2d7fd1d800>) – the function converting importance values to font sizes

  • dpi (float | int | None, default: 384.0) – the dpi value

Return type:

Figure

Returns:

the Figure object to allow you to add further plot elements

diff --git a/moptipyapps.binpacking2d.instgen.html b/moptipyapps.binpacking2d.instgen.html new file mode 100644 index 00000000..6cd33b32 --- /dev/null +++ b/moptipyapps.binpacking2d.instgen.html @@ -0,0 +1,260 @@ +moptipyapps.binpacking2d.instgen package — moptipyapps 0.8.62 documentation

moptipyapps.binpacking2d.instgen package

Tools for generating 2d bin packing instances.

We want to generate instances of the two-dimensional bin packing problem. These instances should have some pre-defined characteristics, e.g., width and height of the bins, number of items to pack, lower bound/optimal number of bins required by any solution, and so on.

At the same time, the instances should be hard.

We treat this whole thing as an optimization problem. Here, given are the pre-defined instance characteristics and the goal is to find instances that are hard to solve.

Important work on this code has been contributed by Mr. Rui ZHAO (赵睿), <zr1329142665@163.com> a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

Submodules

moptipyapps.binpacking2d.instgen.errors module

An objective function counting deviations from the instance definition.

This objective function will be the smaller, the closer the structure of an instance is to the original instance. Due to our encoding, we create instances whose bin width, bin height, and the number of items is the same as in an existing instance. The lower bound for the required number of bins is also the same.

This objective function here also incorporates some additional features, such as:

  1. Is the number of different items similar to those in the original instance? In an existing instance, some items of same width and height could be grouped together. We may have 10 items to pack, but only 3 different item sizes exist. We here compare the number of different item sizes of a generated instance with the number in the instance definition.

  2. In a given instance, we can observe the minimum and maximum width and height of any item. If an item in the generated instance is smaller than the minimum or larger than the maximum permitted value in one dimension, this will increase the error count.

  3. Additionally, we want the actual minimum and maximum width and height of any item in the generated instance is the same as in the original instance.

  4. Finally, we want that the total area covered by all items is the same in the generated instance as in the original instance.

All of these potential violations are counted and added up. Using this objective function should drive the search towards generating instances that are structurally similar to an existing instance, at least in some parameters.

We could push this arbitrarily further, trying to emulate the exact distribution of the item sizes, etc. But this may just lead to the reproduction of the original instance by the search and not add anything interesting.

>>> orig = Instance.from_resource("a04")
+>>> space = InstanceSpace(orig)
+>>> print(f"{space.inst_name!r} with {space.n_different_items}/"
+...       f"{space.n_items} items with area {space.total_item_area} "
+...       f"in {space.min_bins} bins of "
+...       f"size {space.bin_width}*{space.bin_height}.")
+'a04n' with 2/16 items with area 7305688 in 3 bins of size 2750*1220.
+
>>> from moptipyapps.binpacking2d.instgen.inst_decoding import InstanceDecoder
+>>> decoder = InstanceDecoder(space)
+>>> import numpy as np
+>>> x = np.array([ 0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 15/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;15;2750;1220;1101,1098;2750,244;2750,98;1101,171;1649,171;2750,976;441,122;1649,122;2750,10;2750,1,2;2750,3;1649,1098;2750,878;2750,58;660,122
+
>>> errors = Errors(space)
+>>> errors.lower_bound()
+0.0
+>>> errors.upper_bound()
+1.0
+>>> errors.evaluate(y)
+0.3778740795710009
+
>>> y[0] = orig
+>>> errors.evaluate(y)
+0.0
+
class moptipyapps.binpacking2d.instgen.errors.Errors(space)[source]

Bases: Objective

Compute the deviation of an instance from the definition.

evaluate(x)[source]

Compute the deviations from the instance definition.

Parameters:

x (list[Instance] | Instance) – the instance

Return type:

float

Returns:

the number of deviations divided by the maximum of the deviations

is_always_integer()[source]

Return True because there are only integer errors.

Retval False:

always

Return type:

bool

log_parameters_to(logger)[source]

Log the parameters of this instance.

Parameters:

logger (KeyValueLogSection) – the logger

Return type:

None

lower_bound()[source]

Get the lower bound of the instance template deviations.

Returs 0.0:

always

Return type:

float

space: Final[InstanceSpace]

the instance description

upper_bound()[source]

Get the upper bound of the number of deviations.

Returs 1.0:

always

Return type:

float

moptipyapps.binpacking2d.instgen.errors_and_hardness module

A combination of the errors and the hardness objective.

>>> orig = Instance.from_resource("a04")
+>>> space = InstanceSpace(orig)
+>>> print(f"{space.inst_name!r} with {space.n_different_items}/"
+...       f"{space.n_items} items with area {space.total_item_area} "
+...       f"in {space.min_bins} bins of "
+...       f"size {space.bin_width}*{space.bin_height}.")
+'a04n' with 2/16 items with area 7305688 in 3 bins of size 2750*1220.
+
>>> from moptipyapps.binpacking2d.instgen.inst_decoding import InstanceDecoder
+>>> decoder = InstanceDecoder(space)
+>>> import numpy as np
+>>> x = np.array([ 0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 15/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;15;2750;1220;1101,1098;2750,244;2750,98;1101,171;1649,171;2750,976;441,122;1649,122;2750,10;2750,1,2;2750,3;1649,1098;2750,878;2750,58;660,122
+
>>> obj = ErrorsAndHardness(space, max_fes=100)
+>>> obj.lower_bound()
+0.0
+>>> obj.upper_bound()
+1.0
+>>> obj.evaluate(y)
+0.8776461858988774
+
>>> obj.evaluate(orig)
+0.9866528870384179
+
class moptipyapps.binpacking2d.instgen.errors_and_hardness.ErrorsAndHardness(space, max_fes=1000000, n_runs=3, executors=(<function setup_rs_f1>, <function setup_rls_f1>, <function setup_rls_f7>))[source]

Bases: Objective

Compute the errors and hardness.

errors: Final[Errors]

the errors objective

evaluate(x)[source]

Compute the combination of hardness and errors.

Parameters:

x (list[Instance] | Instance) – the instance

Return type:

float

Returns:

the hardness and errors

hardness: Final[Hardness]

the hardness objective function

is_always_integer()[source]

Return False because the hardness function returns float.

Retval False:

always

Return type:

bool

log_parameters_to(logger)[source]

Log the parameters of this instance.

Parameters:

logger (KeyValueLogSection) – the logger

Return type:

None

lower_bound()[source]

Get the lower bound of the instance error and hardness.

Returns 0.0:

always

Return type:

float

upper_bound()[source]

Get the upper bound of the instance error and hardness.

Returns 1.0:

always

Return type:

float

moptipyapps.binpacking2d.instgen.experiment module

An example experiment for generating bin packing instances.

moptipyapps.binpacking2d.instgen.experiment.INNER_MAX_FES: Final[int] = 100000

the maximum number of FEs in for the algorithm runs inside the objective

moptipyapps.binpacking2d.instgen.experiment.INNER_RUNS: Final[int] = 3

the numbe rof runs of the algorithms inside the objective

moptipyapps.binpacking2d.instgen.experiment.MAX_FES: Final[int] = 10000

the maximum number of FEs

moptipyapps.binpacking2d.instgen.experiment.cmaes(problem)[source]

Create the basic BiPop-CMA-ES setup.

Parameters:

problem (Problem) – the problem to solve

Return type:

Execution

Returns:

the execution

moptipyapps.binpacking2d.instgen.experiment.run(base_dir)[source]

Run the experiment.

Parameters:

base_dir (str) – the base directory

Return type:

None

moptipyapps.binpacking2d.instgen.hardness module

An objective function assessing the hardness of an instance.

>>> from moptipyapps.binpacking2d.instgen.instance_space import InstanceSpace
+>>> orig = Instance.from_resource("a04")
+>>> space = InstanceSpace(orig)
+>>> print(f"{space.inst_name!r} with {space.n_different_items}/"
+...       f"{space.n_items} items with area {space.total_item_area} "
+...       f"in {space.min_bins} bins of "
+...       f"size {space.bin_width}*{space.bin_height}.")
+'a04n' with 2/16 items with area 7305688 in 3 bins of size 2750*1220.
+
>>> from moptipyapps.binpacking2d.instgen.inst_decoding import InstanceDecoder
+>>> decoder = InstanceDecoder(space)
+>>> import numpy as np
+>>> x = np.array([ 0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 15/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;15;2750;1220;1101,1098;2750,244;2750,98;1101,171;1649,171;2750,976;441,122;1649,122;2750,10;2750,1,2;2750,3;1649,1098;2750,878;2750,58;660,122
+
>>> hardness = Hardness(max_fes=100)
+>>> hardness.lower_bound()
+0.0
+>>> hardness.upper_bound()
+1.0
+>>> hardness.evaluate(y)
+0.8781459580052053
+
>>> y[0] = orig
+>>> hardness.evaluate(y)
+0.9876395399254564
+
>>> z = Instance.from_compact_str(
+...     "cl04_020_01n;19;100;100;1,10;2,38;2,62;1,4,2;3,38;1,7;27,93;1,62;1,"
+...     "3;13,38;1,38;1,17;1,45;36,62;39,3;1,2;20,10;3,24;12,4")
+>>> hardness.evaluate(z)
+0.9995959203471327
+
moptipyapps.binpacking2d.instgen.hardness.DEFAULT_EXECUTORS: Final[tuple[Callable[[Instance], tuple[Execution, Objective]], ...]] = (<function setup_rs_f1>, <function setup_rls_f1>, <function setup_rls_f7>)

the default executors

class moptipyapps.binpacking2d.instgen.hardness.Hardness(max_fes=1000000, n_runs=3, executors=(<function setup_rs_f1>, <function setup_rls_f1>, <function setup_rls_f7>))[source]

Bases: Objective

Compute the hardness of an instance.

evaluate(x)[source]

Compute the hardness of an instance.

Parameters:

x (list[Instance] | Instance) – the instance

Return type:

float

Returns:

the hardness

executors: Final[tuple[Callable[[Instance], tuple[Execution, Objective]], ...]]

the executors

is_always_integer()[source]

Return False because the hardness function returns float.

Retval False:

always

Return type:

bool

log_parameters_to(logger)[source]

Log the parameters of this instance.

Parameters:

logger (KeyValueLogSection) – the logger

Return type:

None

lower_bound()[source]

Get the lower bound of the instance hardness.

Return type:

float

Returns:

the lower bound for the instance hardness

Returns 0.0:

always

max_fes: Final[int]

the maximum FEs per setup.

n_runs: Final[int]

the maximum FEs per setup.

upper_bound()[source]

Get the upper bound of the instance hardness.

Return type:

float

Returns:

the upper bound for the instance hardness

Returns 1.0:

always

moptipyapps.binpacking2d.instgen.hardness.setup_rls_f1(instance)[source]

Set up the randomized local search for an instance.

Parameters:

instance (Instance) – the instance

Return type:

tuple[Execution, Objective]

Returns:

the execution

moptipyapps.binpacking2d.instgen.hardness.setup_rls_f7(instance)[source]

Set up the randomized local search for an instance.

Parameters:

instance (Instance) – the instance

Return type:

tuple[Execution, Objective]

Returns:

the execution and upper bound of the objective

moptipyapps.binpacking2d.instgen.hardness.setup_rs_f1(instance)[source]

Set up the randomized sampling for an instance.

Parameters:

instance (Instance) – the instance

Return type:

tuple[Execution, Objective]

Returns:

the execution

moptipyapps.binpacking2d.instgen.inst_decoding module

A decoder for 2D BPP instances.

The goal of developing this decoding procedure is that we need a deterministic mapping of some easy-to-process data structure to an instance of the two-dimensional bin packing problem. The instance produced by the mapping should use a pre-defined bin width, bin height, and number of items. It should also have a pre-defined lower bound for the number of bins required and it must be ensured that this lower bound can also be reached, i.e., that at least one solution exists that can indeed pack all items into this number of bins.

As source data structure to be mapped, we choose the real vectors of a fixed length (discussed later on).

The idea is that we take the bin width, bin height, and lower bound of the number of bins (let’s call it lb) from a template instance. We also take the number items (let’s call it n) from that instance.

Now we begin with lb items, each of which exactly of the size and dimensions of a bin. At the end, we want to have n items. To get there, in each step of our decoding, we split one existing item into two items. This means that each step will create one additional item for the instance (while making one existing item smaller). This, in turn, means that we have to do n - lb decoding steps, as we start with lb items and, after n - lb steps, will have lb + n - lb = n items.

So far so good. But how do we split?

Each split that we want to perform be defined by four features:

  1. the index of the item that we are going to split,

  2. whether we split it horizontally or vertically,

  3. where we are going to split it,

  4. and how to continue if the proposed split is not possible, e.g., because it would lead to a zero-width or zero-height item.

Now we can encode this in two real numbers selector and cutter from the interval [-1,1].

First, we multiply the absolute value of the selector with the current number of items that we have. This is initially 2, will then be 3 in the next iteration, then 4, and so on. Converted to an int, the result of this multiplication gives us the index of the item to split.

Then, if cutter >= 0, we will cut the item horizontally. Otherwise, i.e., if cutter < 0, we cut vertically. Where to cut is then decided by multiplying the absolute value of cutter with the length of the item in the selected cutting dimension.

If that is not possible, we move to the next item and try again. If selector < 0, we move towards smaller indices and wrap after 0. Otherwise, we move towards higher indices and wrap at the end of the item list. If we arrive back at the first object, this means that the split was not possible for any of the existing items. We now rotate the split by 90°, i.e., if we tried horizontal splits, we now try vertical ones and vice versa.

It is easy to see that it must always be possible to split at least one item in at least one direction. Since we took the bin dimensions and numbers of items from an existing instance of the benchmark set, it must be possible to divide the bins into n items in one way or another. Therefore, the loop will eventually terminate and yield the right amount of items.

This means that with 2 * (n - lb) floating point numbers, we can describe an instance whose result is a perfect packing, without any wasted space.

Now the benchmark instances are not just instances that can only be solved by perfect packings. Instead, they have some wasted space.

We now want to add some wasted space to our instance. So far, our items occupy exactly lb * bin_width * bin_height space units. We can cut at most bin_width * bin_height - 1 of these units without changing the required bins of the packing: We would end up with (lb - 1) * bin_width * bin_height + 1 space units required by our items, which is 1 too large to fit into lb - 1 bins.

So we just do the same thing again: We again use two real numbers to describe each split. Like before, we loop through these numbers, pick the object to split, and compute where to split it. Just now we throw away the piece that was cut off. (Of course, we compute the split positions such that we never dip under the area requirement discussed above).

This allows us to use additional real variables to define how the space should be reduced. 2 * (n - lb) variables, we get instances requiring perfect packing. With every additional pair of variables, we cut some more space. If we would use 2 * (n - lb) + 10 variables, then we would try to select five items from which we can cut off a bit. This number of additional variables can be chosen by the user.

Finally, we merge all items that have the same dimension into groups, as is the case in some of the original instances. We then shuffle these groups randomly, to give the instances a bit of a more unordered texture. The random shuffling is seeded with the binary representation of the input vectors.

In the end, we have translated a real vector to a two-dimensional bin packing instance. Hurray.

>>> space = InstanceSpace(Instance.from_resource("a04"))
+>>> print(f"{space.inst_name!r} with {space.n_different_items}/"
+...       f"{space.n_items} items with area {space.total_item_area} "
+...       f"in {space.min_bins} bins of "
+...       f"size {space.bin_width}*{space.bin_height}.")
+'a04n' with 2/16 items with area 7305688 in 3 bins of size 2750*1220.
+
>>> decoder = InstanceDecoder(space)
+>>> import numpy as np
+>>> x = np.array([ 0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, -0.1,  0.3,  0.5, -0.6, -0.7,  0.9,
+...                0.0,  0.2, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 15/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;15;2750;1220;1101,1098;2750,244;2750,98;1101,171;1649,171;2750,976;441,122;1649,122;2750,10;2750,1,2;2750,3;1649,1098;2750,878;2750,58;660,122
+
>>> x = np.array([ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 3/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;3;2750;1220;2750,1216,2;2750,1,13;2750,1215
+
>>> from math import nextafter
+>>> a1 = nextafter(1.0, -10)
+>>> x = np.array([ a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 4/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;4;2750;1220;2750,1208;2750,1219;2750,1220;2750,1,13
+
>>> from math import nextafter
+>>> a1 = nextafter(-1.0, 10)
+>>> x = np.array([ a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 5/16 items with area 10065000 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;5;2750;1220;2750,1220;2730,1220;2748,1220;1,1220,4;2,1220,9
+
>>> from math import nextafter
+>>> a1 = nextafter(-1.0, 10)
+>>> x = np.array([ a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, 0.3, 0.7 ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 6/16 items with area 10064146 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;6;2750;1220;2,1220,9;1,1220,3;2748,1220;1,366;2750,1220;2730,1220
+
>>> from math import nextafter
+>>> a1 = nextafter(-1.0, 10)
+>>> x = np.array([ a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, a1, a1, a1, a1, a1, a1,
+...                a1, a1, 0.3, 0.7, -0.2, -0.3,
+...                0.5, -0.3])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 6/16 items with area 10061706 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;6;2750;1220;2,1220,7;2750,1220;2730,1220;1,1220,5;1,366;2748,1220
+
>>> x = np.array([ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0,
+...                0.0, 0.0, ])
+>>> y = space.create()
+>>> decoder.decode(x, y)
+>>> space.validate(y)
+>>> res: Instance = y[0]
+>>> print(f"{res.name!r} with {res.n_different_items}/"
+...       f"{res.n_items} items with area {res.total_item_area} "
+...       f"in {res.lower_bound_bins} bins of "
+...       f"size {res.bin_width}*{res.bin_height}.")
+'a04n' with 5/16 items with area 9910948 in 3 bins of size 2750*1220.
+>>> print(space.to_str(y))
+a04n;5;2750;1220;2698,1;2750,1,12;2750,1216;2750,1215;2750,1160
+
class moptipyapps.binpacking2d.instgen.inst_decoding.InstanceDecoder(space)[source]

Bases: Encoding

Decode a string of n real values in [0,1] to an instance.

decode(x, y)[source]

Decode the real-valued array to a 2D BPP instance.

Parameters:
Return type:

None

get_x_dim(slack=0)[source]

Get the minimum dimension that a real vector must have.

Parameters:

slack (float | int, default: 0) – a parameter denoting the amount of slack for reducing the item size

Return type:

int

Returns:

the minimum dimension

space: Final[InstanceSpace]

the instance description

moptipyapps.binpacking2d.instgen.instance_space module

An encoding that is inspired by a given instance.

class moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace(source)[source]

Bases: Space

An space structure for the instance generation problem and space.

bin_height: Final[int]

the bin height

bin_width: Final[int]

the bin width

copy(dest, source)[source]

Copy the instance list.

Parameters:
Return type:

None

create()[source]

Generate a list for receiving an instance.

Return type:

list[Instance]

Returns:

the new instance list

from_str(text)[source]

Convert an instance string to a list with an instance.

Parameters:

text (str) – the input string

Return type:

list[Instance]

Returns:

the element in the space corresponding to text

inst_name: Final[str]

the instance name

is_equal(x1, x2)[source]

Check if the contents of two instances of the data structure are equal.

Parameters:
Return type:

bool

Returns:

True if the contents are equal, False otherwise

item_height_max: Final[int]

the maximum item height

item_height_min: Final[int]

the minimum item height

item_width_max: Final[int]

the maximum item width

item_width_min: Final[int]

the minimum item width

log_parameters_to(logger)[source]

Log the parameters of this instance.

Parameters:

logger (KeyValueLogSection) – the logger

Return type:

None

min_bins: Final[int]

the minimum number of bins that this instance requires

n_different_items: Final[int]

the target number of unique items

n_items: Final[int]

the target number of items (including repetitions)

n_points()[source]

Get the approximate number of different elements in the space.

Return type:

int

Returns:

the approximate scale of the space

to_str(x)[source]

Convert an instance list to a string.

Parameters:

x (list[Instance]) – the instance list

Return type:

str

Returns:

the string representation of x

total_item_area: Final[int]

the total item area

validate(x)[source]

Check whether a given point in the space is valid.

Parameters:

x (list[Instance]) – the point

Raises:
  • TypeError – if the point x (or one of its elements, if applicable) has the wrong data type

  • ValueError – if the point x is invalid and/or simply is not an element of this space

Return type:

None

moptipyapps.binpacking2d.instgen.problem module

An instance of the instance generation problem.

class moptipyapps.binpacking2d.instgen.problem.Problem(name, slack)[source]

Bases: Component

An instance of the 2D Bin Packing Instance Generation Problem.

encoding: Final[InstanceDecoder]

the internal decoding

search_space: Final[VectorSpace]

the search space

solution_space: Final[InstanceSpace]

the instance to be used as template

diff --git a/moptipyapps.binpacking2d.objectives.html b/moptipyapps.binpacking2d.objectives.html new file mode 100644 index 00000000..99f7d31b --- /dev/null +++ b/moptipyapps.binpacking2d.objectives.html @@ -0,0 +1,288 @@ +moptipyapps.binpacking2d.objectives package — moptipyapps 0.8.62 documentation

moptipyapps.binpacking2d.objectives package

Different objective functions for two-dimensional bin packing.

The following objective functions are implemented:

  • bin_count returns the number of bins occupied by a given packing.

  • bin_count_and_empty returns a combination of the number of bins occupied by a given packing and the fewest number of objects located in any bin.

  • bin_count_and_last_empty returns a combination of the number of bins occupied by a given packing and the number of objects located in the last bin.

  • bin_count_and_small returns a combination of the number of bins occupied by a given packing and the smallest area occupied by objects in any bin.

  • bin_count_and_last_small returns a combination of the number of bins occupied by a given packing and the area occupied by the objects in the last bin.

  • bin_count_and_lowest_skyline returns a combination of the number of bins occupied by a given packing and the smallest area under the skyline in any bin, where the “skyline” is the upper border of the space occupied by objects.

  • bin_count_and_last_skyline returns a combination of the number of bins occupied by a given packing and the smallest area under the skyline in the last bin, where the “skyline” is the upper border of the space occupied by objects.

Important initial work on this code has been contributed by Mr. Rui ZHAO (赵睿), <zr1329142665@163.com> a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

Submodules

moptipyapps.binpacking2d.objectives.bin_count module

An objective function for minimizing the number of bins in packings.

This function returns the number of bins.

moptipyapps.binpacking2d.objectives.bin_count.BIN_COUNT_NAME: Final[str] = 'binCount'

the name of the bin count objective function

class moptipyapps.binpacking2d.objectives.bin_count.BinCount(instance)[source]

Bases: Objective

Compute the number of bins.

evaluate(x)[source]

Get the number of bins.

Parameters:

x – the packing

Return type:

int

Returns:

the number of bins used

is_always_integer()[source]

Return True because there are only integer bins.

Retval True:

always

Return type:

bool

lower_bound()[source]

Get the lower bound of the number of bins objective.

Return type:

int

Returns:

the lower bound for the number of required bins, i.e., lower_bound_bins

>>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]])
+>>> ins.lower_bound_bins
+1
+>>> BinCount(ins).lower_bound()
+1
+
>>> ins = Instance("b", 10, 50, [[10, 5, 10], [3, 3, 1], [5, 5, 1]])
+>>> ins.lower_bound_bins
+2
+>>> BinCount(ins).lower_bound()
+2
+
>>> ins = Instance("c", 10, 50, [[10, 5, 20], [30, 3, 10], [5, 5, 1]])
+>>> ins.lower_bound_bins
+4
+>>> BinCount(ins).lower_bound()
+4
+
to_bin_count(z)[source]

Get the bin count corresponding to an objective value.

Parameters:

z (int)

Return type:

int

Returns:

the value itself

upper_bound()[source]

Get the upper bound of the number of bins.

Return type:

int

Returns:

the number of items in the instance, i.e., n_items

>>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]])
+>>> ins.n_items
+3
+>>> BinCount(ins).upper_bound()
+3
+
>>> ins = Instance("b", 10, 50, [[10, 5, 10], [3, 3, 1], [5, 5, 1]])
+>>> ins.n_items
+12
+>>> BinCount(ins).upper_bound()
+12
+
>>> ins = Instance("c", 10, 50, [[10, 5, 20], [30, 3, 10], [5, 5, 1]])
+>>> ins.n_items
+31
+>>> BinCount(ins).upper_bound()
+31
+

moptipyapps.binpacking2d.objectives.bin_count_and_empty module

An objective function indirectly minimizing the number of bins in packings.

This objective function first computes the number of bins used. Let’s call it n_bins. We know the total number of items, n_items, as well (because this is also the number of rows in the packing). Now we return (n_items * (n_bins - 1)) + min_items, where min_items is the number of items in the bin with the fewest items.

This is similar to bin_count_and_last_small, but instead of focussing on the very last bin, it uses the minimum element count over all bins. It has the same lower- and upper bound, though.

class moptipyapps.binpacking2d.objectives.bin_count_and_empty.BinCountAndEmpty(instance)[source]

Bases: BinCountAndLastEmpty

Get the number of bins and number of elements in the emptiest one.

evaluate(x)[source]

Evaluate the objective function.

Parameters:

x – the solution

Return type:

int

Returns:

the bin size and smallest-bin-area factor

moptipyapps.binpacking2d.objectives.bin_count_and_empty.bin_count_and_empty(y, temp)[source]

Get the number of bins and number of elements in the emptiest one.

We compute the total number of bins minus 1 and multiply it with the number of items. We then add the smallest number of elements in any bin.

Parameters:
Return type:

int

Returns:

the objective value

>>> tempa = np.empty(10, int)
+>>> bin_count_and_empty(np.array([[1, 1, 10, 10, 20, 20],
+...                               [1, 1, 30, 30, 40, 40],
+...                               [1, 1, 20, 20, 30, 30]], int), tempa)
+3
+>>> bin_count_and_empty(np.array([[1, 1, 10, 10, 20, 20],
+...                               [1, 2, 30, 30, 40, 40],  # bin 2!
+...                               [1, 1, 20, 20, 30, 30]], int), tempa)
+4
+>>> bin_count_and_empty(np.array([[1, 2, 10, 10, 20, 20],  # bin 2!
+...                               [1, 2, 30, 30, 40, 40],  # bin 2!
+...                               [1, 1, 20, 20, 30, 30]], int), tempa)
+4
+>>> bin_count_and_empty(np.array([[1, 3, 10, 10, 20, 20],  # bin 3!
+...                               [1, 2, 30, 30, 40, 40],  # bin 2!
+...                               [1, 1, 20, 20, 30, 30]], int), tempa)
+7
+

moptipyapps.binpacking2d.objectives.bin_count_and_last_empty module

An objective function indirectly minimizing the number of bins in packings.

This objective function first computes the number of bins used. Let’s call it n_bins. We know the total number of items, n_items, as well (because this is also the number of rows in the packing). Now we return (n_items * (n_bins - 1)) + number_of_items_in_last_bin, where number_of_items_in_last_bin is, well, the number of items in the very last bin.

The idea behind this is: If one of two packings has the smaller number of bins, then this one will always have the smaller objective value. If two packings have the same number of bins, but one has fewer items in the very last bin, then that one is better. With this mechanism, we drive the search towards “emptying” the last bin. If the number of items in the last bin would reach 0, that last bin would disappear - and we have one bin less.

class moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty(instance)[source]

Bases: BinCount

Compute the number of bins and the emptiness of the last one.

evaluate(x)[source]

Evaluate the objective function.

Parameters:

x – the solution

Return type:

int

Returns:

the bin size and emptyness factor

lower_bound()[source]

Get the lower bound of the number of bins and emptiness objective.

We know from the instance (lower_bound_bins) that we require at least as many bins such that they can accommodate the total area of all items together. Let’s call this number lb. Now if lb is one, then all objects could be in the first bin, in which case the objective value would equal to n_items, i.e., the total number of items in the first = last bin. If it is lb=2, then we know that we will need at least two bins. The best case would be that n_items - 1 items are in the first bin and one is in the last bin. This means that we would get 1 * n_items + 1 as objective value. If we have lb=3 bins, then we could have n_items - 1 items distributed over the first two bins with one item left over in the last bin, i.e., would get (2 * n_items) + 1. And so on.

Return type:

int

Returns:

max(n_items, (lb - 1) * n_items + 1)

>>> from moptipyapps.binpacking2d.instance import Instance
+>>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]])
+>>> ins.n_items
+3
+>>> ins.lower_bound_bins
+1
+>>> BinCountAndLastEmpty(ins).lower_bound()
+3
+
>>> ins = Instance("b", 10, 50, [[10, 5, 10], [3, 3, 1], [5, 5, 1]])
+>>> ins.n_items
+12
+>>> ins.lower_bound_bins
+2
+>>> BinCountAndLastEmpty(ins).lower_bound()
+13
+
>>> ins = Instance("c", 10, 50, [[10, 5, 20], [30, 3, 10], [5, 5, 1]])
+>>> ins.n_items
+31
+>>> ins.lower_bound_bins
+4
+>>> BinCountAndLastEmpty(ins).lower_bound()
+94
+
to_bin_count(z)[source]

Convert an objective value to a bin count.

Parameters:

z (int) – the objective value

Return type:

int

Returns:

the bin count

upper_bound()[source]

Get the upper bound of the number of bins plus emptiness.

Return type:

int

Returns:

the number of items in the instance to the square

>>> from moptipyapps.binpacking2d.instance import Instance
+>>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]])
+>>> ins.n_items
+3
+>>> BinCountAndLastEmpty(ins).upper_bound()
+9
+
>>> ins = Instance("b", 10, 50, [[10, 5, 10], [3, 3, 1], [5, 5, 1]])
+>>> ins.n_items
+12
+>>> BinCountAndLastEmpty(ins).upper_bound()
+144
+
>>> ins = Instance("c", 10, 50, [[10, 5, 20], [30, 3, 10], [5, 5, 1]])
+>>> ins.n_items
+31
+>>> BinCountAndLastEmpty(ins).upper_bound()
+961
+
moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.bin_count_and_last_empty(y)[source]

Compute the number of bins and the emptiness of the last bin.

We compute the total number of bins minus 1 and multiply it with the number of items. We then add the number of items in the last bin.

Parameters:

y (ndarray) – the packing

Return type:

int

Returns:

the objective value

>>> bin_count_and_last_empty(np.array([[1, 1, 10, 10, 20, 20],
+...                                    [1, 1, 30, 30, 40, 40],
+...                                    [1, 1, 20, 20, 30, 30]], int))
+3
+>>> bin_count_and_last_empty(np.array([[1, 1, 10, 10, 20, 20],
+...                                    [1, 2, 30, 30, 40, 40],  # bin 2!
+...                                    [1, 1, 20, 20, 30, 30]], int))
+4
+>>> bin_count_and_last_empty(np.array([[1, 2, 10, 10, 20, 20],  # bin 2!
+...                                    [1, 2, 30, 30, 40, 40],  # bin 2!
+...                                    [1, 1, 20, 20, 30, 30]], int))
+5
+>>> bin_count_and_last_empty(np.array([[1, 3, 10, 10, 20, 20],  # bin 3!
+...                                    [1, 2, 30, 30, 40, 40],  # bin 2!
+...                                    [1, 1, 20, 20, 30, 30]], int))
+7
+

moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline module

An objective function indirectly minimizing the number of bins in packings.

This objective function minimizes the number of bins and maximizes the “usable” space in the last bin.

Which space is actually useful for our encodings? Let’s say we have filled a bin to a certain degree and somewhere there is a “hole” in the filled area, but this hole is covered by another object. The area of the hole is not used, but it also cannot be used anymore. The area that we can definitely use is the area above the “skyline” of the objects in the bin. The skyline at any horizontal x coordinate be the highest border of any object that intersects with x horizontally. In other words, it is the y value at and above which no other object is located at this x coordinate. The area below the skyline cannot be used anymore. The area above the skyline can.

If we minimize the area below the skyline in the very last bin, then this will a similar impact as minimizing the overall object area in the last bin (see bin_count_and_last_small). We push the skyline lower and lower and, if we are lucky, the last bin eventually becomes empty.

The objective bin_count_and_lowest_skyline works quite similarly to this one, but minimizes the lowest skyline over any bin.

class moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.BinCountAndLastSkyline(instance)[source]

Bases: BinCountAndLastSmall

Compute the number of bins and the useful area in the last bin.

evaluate(x)[source]

Evaluate the objective function.

Parameters:

x – the solution

Return type:

int

Returns:

the bin size and last-bin-small-area factor

moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.bin_count_and_last_skyline(y, bin_width, bin_height)[source]

Compute the bin count-1 times the bin size + the space below the skyline.

Parameters:
  • y (ndarray) – the packing

  • bin_width (int) – the bin width

  • bin_height (int) – the bin height

Return type:

int

Returns:

the objective value

>>> 10*0 + 10*20 + 10*30 + 10*40 + 10*0
+900
+>>> bin_count_and_last_skyline(np.array([[1, 1, 10, 10, 20, 20],
+...                                      [1, 1, 30, 30, 40, 40],
+...                                      [1, 1, 20, 20, 30, 30]], int),
+...                            50, 50)
+900
+>>> 5 * 0 + 5 * 10 + 10 * 20 + 5 * 10 + 25 * 0
+300
+>>> bin_count_and_last_skyline(np.array([[1, 1,  5,  0, 15, 10],
+...                                      [1, 1, 10, 10, 20, 20],
+...                                      [1, 1, 15,  0, 25, 10]], int),
+...                            50, 50)
+300
+>>> 50*50 + 0*10 + 10*20 + 30*0
+2700
+>>> bin_count_and_last_skyline(np.array([[1, 1,  5,  0, 15, 10],
+...                                      [1, 2, 10, 10, 20, 20],
+...                                      [1, 1, 15,  0, 25, 10]], int),
+...                            50, 50)
+2700
+>>> 5 * 0 + 5 * 10 + 3 * 20 + (50 - 13) * 25
+1035
+>>> bin_count_and_last_skyline(np.array([[1, 1,  5,  0, 15, 10],
+...                                      [1, 1, 10, 10, 20, 20],
+...                                      [1, 1, 15,  0, 25, 10],
+...                                      [2, 1, 13, 20, 50, 25]], int),
+...                            50, 50)
+1035
+>>> 50*50*3 + 25*50
+8750
+>>> bin_count_and_last_skyline(np.array([[1, 1, 0, 0, 10, 10],
+...                                      [2, 2, 0, 0, 20, 20],
+...                                      [3, 3, 0, 0, 25, 10],
+...                                      [4, 4, 0, 0, 50, 25]], int),
+...                            50, 50)
+8750
+>>> 50*50*3 + 25*10 + 25*0
+7750
+>>> bin_count_and_last_skyline(np.array([[1, 1, 0, 0, 10, 10],
+...                                      [2, 2, 0, 0, 20, 20],
+...                                      [3, 4, 0, 0, 25, 10],
+...                                      [4, 3, 0, 0, 50, 25]], int),
+...                            50, 50)
+7750
+>>> 50*50*3 + 20*20 + 30*0
+7900
+>>> bin_count_and_last_skyline(np.array([[1, 1, 0, 0, 10, 10],
+...                                      [2, 4, 0, 0, 20, 20],
+...                                      [3, 2, 0, 0, 25, 10],
+...                                      [4, 3, 0, 0, 50, 25]], int),
+...                            50, 50)
+7900
+>>> 50*50*3 + 20*10 + 30*20 + 20*0
+8300
+>>> bin_count_and_last_skyline(np.array([[1, 1, 0, 0, 10, 10],
+...                                      [2, 4, 0, 0, 20, 20],
+...                                      [3, 2, 0, 0, 25, 10],
+...                                      [2, 4, 10, 20, 30, 30],
+...                                      [4, 3, 0, 0, 50, 25]], int),
+...                            50, 50)
+8300
+

moptipyapps.binpacking2d.objectives.bin_count_and_last_small module

An objective function indirectly minimizing the number of bins in packings.

This objective function computes the number of bins used. Let’s call it n_bins. We know the area bin_area of a bin as well. Now we return (bin_area * (n_bins - 1)) + area_of_items_in_last_bin, where area_of_items_in_last_bin is, well, the area covered by items in the very last bin.

The idea behind this is: If one of two packings has the smaller number of bins, then this one will always have the smaller objective value. If two packings have the same number of bins, but one requires less space in the very last bin, then that one is better. With this mechanism, we drive the search towards “emptying” the last bin. If the number of items in the last bin would reach 0, that last bin would disappear - and we have one bin less.

This objective is similar to bin_count_and_small, with the difference that it focuses on the last bin whereas bin_count_and_small tries to minimize the area in any bin.

class moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall(instance)[source]

Bases: BinCount

Compute the number of bins and the area in the last one.

evaluate(x)[source]

Evaluate the objective function.

Parameters:

x – the solution

Return type:

int

Returns:

the bin size and last-bin-small-area factor

lower_bound()[source]

Get the lower bound of the number of bins and small-size objective.

We know from the instance (lower_bound_bins) that we require at least as many bins such that they can accommodate the total area of all items together. Let’s call this number lb. Now if lb is one, then all objects could be in the first bin, in which case the objective value would equal to the total area of all items (total_item_area). If it is lb=2, then we know that we will need at least two bins. The best case would be that almost all items are in the first bin and only the smallest object is in the last bin. This means that we would get 1 * bin_area + smallest_area as objective value. If we have lb=3 bins, then we could again have all but the smallest items distributed over the first two bins and only the smallest one in the last bin, i.e., would get (2 * bin_area) + smallest_area. And so on.

Return type:

int

Returns:

total_item_area if the lower bound lb of the number of bins is 1, else (lb - 1) * bin_area + smallest_area, where bin_area is the area of a bin, total_item_area is the area of all items added up, and smallest_area is the area of the smallest item

>>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]])
+>>> ins.total_item_area
+84
+>>> ins.lower_bound_bins
+1
+>>> BinCountAndLastSmall(ins).lower_bound()
+84
+
>>> ins = Instance("b", 10, 50, [[10, 5, 10], [3, 3, 1], [5, 5, 1]])
+>>> ins.total_item_area
+534
+>>> ins.lower_bound_bins
+2
+>>> BinCountAndLastSmall(ins).lower_bound()
+509
+
>>> ins = Instance("c", 10, 50, [[10, 5, 10], [30, 3, 1], [5, 5, 1]])
+>>> ins.total_item_area
+615
+>>> ins.lower_bound_bins
+2
+>>> BinCountAndLastSmall(ins).lower_bound()
+525
+
to_bin_count(z)[source]

Convert an objective value to a bin count.

Parameters:

z (int) – the objective value

Return type:

int

Returns:

the bin count

upper_bound()[source]

Get the upper bound of this objective function.

Return type:

int

Returns:

a very coarse estimate of the upper bound

>>> ins = Instance("a", 100, 50, [[10, 5, 1], [3, 3, 1], [5, 5, 1]])
+>>> ins.n_items
+3
+>>> BinCountAndLastSmall(ins).upper_bound()
+15000
+
>>> ins = Instance("b", 10, 50, [[10, 5, 10], [3, 3, 1], [5, 5, 1]])
+>>> ins.n_items
+12
+>>> BinCountAndLastSmall(ins).upper_bound()
+6000
+
>>> ins = Instance("c", 10, 50, [[10, 5, 10], [30, 3, 1], [5, 5, 10]])
+>>> ins.n_items
+21
+>>> BinCountAndLastSmall(ins).upper_bound()
+10500
+
moptipyapps.binpacking2d.objectives.bin_count_and_last_small.bin_count_and_last_small(y, bin_area)[source]

Compute the number of bins and the occupied area in the last bin.

We compute the total number of bins minus 1 and multiply it with the total area of items. We then add the area of items in the last bin.

Parameters:
  • y (ndarray) – the packing

  • bin_area (int) – the area of a single bin

Return type:

int

Returns:

the objective value

>>> bin_count_and_last_small(np.array([[1, 1, 10, 10, 20, 20],
+...                                    [1, 1, 30, 30, 40, 40],
+...                                    [1, 1, 20, 20, 30, 30]], int),
+...                                    50*50)
+300
+>>> bin_count_and_last_small(np.array([[1, 1, 10, 10, 20, 20],
+...                                    [1, 2, 30, 30, 40, 40],  # bin 2!
+...                                    [1, 1, 20, 20, 30, 30]], int),
+...                                    50*50)
+2600
+>>> bin_count_and_last_small(np.array([[1, 2, 10, 10, 20, 20],  # bin 2!
+...                                    [1, 2, 30, 30, 40, 40],  # bin 2!
+...                                    [1, 1, 20, 20, 30, 30]], int),
+...                                    50*50)
+2700
+>>> bin_count_and_last_small(np.array([[1, 3, 10, 10, 20, 20],  # bin 3!
+...                                    [1, 2, 30, 30, 40, 40],  # bin 2!
+...                                    [1, 1, 20, 20, 30, 30]], int),
+...                                    50*50)
+5100
+>>> bin_count_and_last_small(np.array([[1, 3, 10, 10, 50, 50],  # bin 3!
+...                                    [1, 2, 30, 30, 60, 60],  # bin 2!
+...                                    [1, 1, 20, 20, 30, 30]], np.int8),
+...                                    50*50)
+6600
+

moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline module

An objective function indirectly minimizing the number of bins in packings.

This objective function minimizes the number of bins and maximizes the “useable” space in any bin.

Which space is actually useful for our encodings? Let’s say we have filled a bin to a certain degree and somewhere there is a “hole” in the filled area, but this hole is covered by another object. The area of the hole is not used, but it also cannot be used anymore. The area that we can definitely use is the area above the “skyline” of the objects in the bin. The skyline at any horizontal x coordinate be the highest border of any object that intersects with x horizontally. In other words, it is the y value at and above which no other object is located at this x coordinate. The area below the skyline cannot be used anymore. The area above the skyline can.

If we minimize the area below the skyline in the very last bin, then this will a similar impact as minimizing the overall object area in the last bin (see bin_count_and_last_small). We push the skyline lower and lower and, if we are lucky, the last bin eventually becomes empty. This is done with the objective bin_count_and_last_skyline.

But we could also minimize the area under the skylines in the other bins. Because a) if we can get any skyline in any bin to become 0, then this bin disappears and b) if we can free up space in the bins by lowering the skylines, then we have a better chance to move an object from the next bin forward into that bin, which increases the chance to make that bin empty.

In this objective function, we therefore use the smallest skyline area over all bins to distinguish between packings of the same number of bins. For all intents and purposes, it has the same lower and upper bound as the bin_count_and_last_small objective.

class moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.BinCountAndLowestSkyline(instance)[source]

Bases: BinCountAndLastSmall

Compute the number of bins and the largest useful area.

evaluate(x)[source]

Evaluate the objective function.

Parameters:

x – the solution

Return type:

int

Returns:

the bin size and last-bin-small-area factor

moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.bin_count_and_lowest_skyline(y, bin_width, bin_height)[source]

Compute the bin count-1 times the bin size + the space below the skyline.

Parameters:
  • y (ndarray) – the packing

  • bin_width (int) – the bin width

  • bin_height (int) – the bin height

Return type:

int

Returns:

the objective value

>>> 10*0 + 10*20 + 10*30 + 10*40 + 10*0
+900
+>>> bin_count_and_lowest_skyline(np.array([[1, 1, 10, 10, 20, 20],
+...                                        [1, 1, 30, 30, 40, 40],
+...                                        [1, 1, 20, 20, 30, 30]], int),
+...                              50, 50)
+900
+>>> 5 * 0 + 5 * 10 + 10 * 20 + 5 * 10 + 25 * 0
+300
+>>> bin_count_and_lowest_skyline(np.array([[1, 1,  5,  0, 15, 10],
+...                                        [1, 1, 10, 10, 20, 20],
+...                                        [1, 1, 15,  0, 25, 10]], int),
+...                              50, 50)
+300
+>>> 50*50 + min(5*0 + 10*10 + 10*10 + 25*0, 10*0 + 10*20 + 30*0)
+2700
+>>> bin_count_and_lowest_skyline(np.array([[1, 1,  5,  0, 15, 10],
+...                                        [1, 2, 10, 10, 20, 20],
+...                                        [1, 1, 15,  0, 25, 10]], int),
+...                              50, 50)
+2700
+>>> 5 * 0 + 5 * 10 + 3 * 20 + (50 - 13) * 25
+1035
+>>> bin_count_and_lowest_skyline(np.array([[1, 1,  5,  0, 15, 10],
+...                                        [1, 1, 10, 10, 20, 20],
+...                                        [1, 1, 15,  0, 25, 10],
+...                                        [2, 1, 13, 20, 50, 25]], int),
+...                              50, 50)
+1035
+>>> 2500*3 + min(10*10, 20*20, 25*10, 50*25)
+7600
+>>> bin_count_and_lowest_skyline(np.array([[1, 1, 0, 0, 10, 10],
+...                                        [2, 2, 0, 0, 20, 20],
+...                                        [3, 3, 0, 0, 25, 10],
+...                                        [4, 4, 0, 0, 50, 25]], int),
+...                              50, 50)
+7600
+

moptipyapps.binpacking2d.objectives.bin_count_and_small module

An objective function indirectly minimizing the number of bins in packings.

This objective function computes the number of bins used. Let’s call it n_bins. We know the area bin_area of a bin as well. We then compute the minimum area occupied in any bin, min_area. Now we return (bin_area * (n_bins - 1)) + min_area.

This function always prefers a packing that has fewer bins over a packing with more bins duw to the term bin_area * (n_bins - 1) and bin_area >= min_area (let us ignore the case where bin_area == min_area, which does not make practical sense). Since min_area < bin_area in all practically relevant cases, the offset min_area just distinguishes packings that have same number of bins. Amongst such packings, those whose least-occupied bin is closer to being empty are preferred (regardless where this bin is). The idea is that this will eventually allow us to get rid of that least-occupied bin in subsequent optimization steps, i.e., to reduce the number of bins.

This is similar to the bin_count_and_small objective, except that we do not try to expunge the last bin, but any bin. It has the same lower and upper bound, though.

class moptipyapps.binpacking2d.objectives.bin_count_and_small.BinCountAndSmall(instance)[source]

Bases: BinCountAndLastSmall

Compute the number of bins and the area in the smallest one.

evaluate(x)[source]

Evaluate the objective function.

Parameters:

x – the solution

Return type:

int

Returns:

the bin size and smallest-bin-area factor

moptipyapps.binpacking2d.objectives.bin_count_and_small.bin_count_and_small(y, bin_area, temp)[source]

Compute the number of bins and the smallest occupied area in any bin.

We compute the total number of bins minus 1 and multiply it with the total area of items. We then add the area of items in the smallest bin.

Parameters:
  • y (ndarray) – the packing

  • bin_area (int) – the area of a single bin

  • temp (ndarray) – a temporary array to hold the current area counters

Return type:

int

Returns:

the objective value

>>> tempa = np.empty(10, int)
+>>> bin_count_and_small(np.array([[1, 1, 10, 10, 20, 20],
+...                               [1, 1, 30, 30, 40, 40],
+...                               [1, 1, 20, 20, 30, 30]], int),
+...                              50*50, tempa)
+300
+>>> bin_count_and_small(np.array([[1, 1, 10, 10, 20, 20],
+...                               [1, 2, 30, 30, 40, 40],  # bin 2!
+...                               [1, 1, 20, 20, 30, 30]], int),
+...                              50*50, tempa)
+2600
+>>> bin_count_and_small(np.array([[1, 2, 10, 10, 20, 20],  # bin 2!
+...                               [1, 2, 30, 30, 40, 40],  # bin 2!
+...                               [1, 1, 20, 20, 30, 30]], int),
+...                               50*50, tempa)
+2600
+>>> bin_count_and_small(np.array([[1, 3, 10, 10, 20, 20],  # bin 3!
+...                               [1, 2, 30, 30, 40, 40],  # bin 2!
+...                               [1, 1, 20, 20, 30, 30]], int),
+...                              50*50, tempa)
+5100
+>>> bin_count_and_small(np.array([[1, 3, 10, 10, 50, 50],  # bin 3!
+...                               [1, 2, 30, 30, 60, 60],  # bin 2!
+...                               [1, 1, 20, 20, 30, 30]], np.int8),
+...                              50*50, tempa)
+5100
+
diff --git a/moptipyapps.dynamic_control.controllers.html b/moptipyapps.dynamic_control.controllers.html new file mode 100644 index 00000000..9dc2b0ad --- /dev/null +++ b/moptipyapps.dynamic_control.controllers.html @@ -0,0 +1 @@ +moptipyapps.dynamic_control.controllers package — moptipyapps 0.8.62 documentation

moptipyapps.dynamic_control.controllers package

Several possible controllers for the dynamic control scenarios.

  • linear offers simple linear controllers.

  • quadratic offers simple quadratic controllers.

  • cubic offers simple cubic controllers.

  • ann offers poor man’s artificial neural networks (ANNs), i.e., networks represented as plain functions whose parameter vectors are subject to optimization.

  • predefined provides a set of controllers taken from the works of NOACK, CORNEJO MACEDA, LI, and SUN.

  • partially_linear offers several linear controllers anchored at certain points in the state space and always uses the controller closest to the current state.

  • min_ann offers controllers similar to min_ann, but instead of using the ANN output as controller value, these ANNs have an additional input z and then use the value z* as controller output for which the ANNs take on the smallest value.

  • peaks provides controllers similar to ANNs but using peak functions (exp(-a²)) as activation functions.

These controller blueprints can be used in two ways. The basic use case is to synthesize, well, controllers for the dynamic system. In the dynamic system controller synthesis problem, we have a system of differential equations D=ds/dt where D depends on the current system state s and the output of a controller c. So D is actually a function D(s, c). Now the goal is to get the dynamic system to move into a nice and stable state. We want to find a controller c that can do this. Now c is actually a function c(s, p) that takes as input the current system state s and its own parameterization p. Imagine c to be, for example, an artificial neural network (an ann), then p is its weight vector and p is subject to optimization. We would try to find the values p that minimize a certain objective function that could, let’s say, represent the cost or energy in the system. So use case number one of our controllers is to represent the controller blueprints for this process.

Use case number two is to use the controllers as blueprints for system models M. Basically, a system model M should be a drop-in replacement for the system equations D. In the real world, obviously, we do not have the system equations that govern a complex system like a helicopter or something. But we can test a helicopter controller in a wind tunnel. In such a scenario, evaluating the objective function is very costly. If, instead, we could learn a model M that can reasonably accurately describe the helicopter-experiment behavior D, then we could replace the actual experiment with a simulation. Since computing power is cheap, that would be awesome. Now it turns out that the structure of the equations, i.e., a parameterized function relating inputs to outputs, that we need for this, is pretty much the same as in use case number one above. So we can re-use the same code, the basic controller blueprints, also for the purpose of synthesizing models using a model_objective function.

Submodules

moptipyapps.dynamic_control.controllers.ann module

Poor man’s Artificial Neural Networks.

Here, artificial neural networks (ANNs) are defined as plain mathematical functions which are parameterized by their weights. The weights are subject to black-box optimization and all together put into a single vector. In other words, we do not use proper back-propagation learning or any other sophisticated neural network specific training strategy. Instead, we treat the neural networks as black boxes that can be parameterized using the weight vector. Different ANN architectures have different weight vectors. As activation functions, we use arctan.

The neural networks here are automatically generated via code generation provided by module codegen. This allows us to have networks of arbitrary shape for arbitrary input and output dimensions. Since code generation followed by on-the-fly compilation via numba is time and memory consuming, we cache all neural network-based instances of Controller.

moptipyapps.dynamic_control.controllers.ann.anns(system)[source]

Create poor man’s ANNs fitting to a given system.

Based on the dimensionality of the state space, we generate a set of ANNs with different numbers of layers and neurons. The weights of the neurons can then directly be optimized by a numerical optimization algorithm. This is, of course, probably much less efficient that doing some proper learning like back-propagation. However, it allows us to easily plug the ANNs into the same optimization routines as other controllers.

Parameters:

system (System) – the equations object

Return type:

Iterable[Controller]

Returns:

the ANNs

moptipyapps.dynamic_control.controllers.ann.make_ann(state_dims, control_dims, layers)[source]

Dynamically create an ANN.

Parameters:
  • state_dims (int) – the state or input dimension

  • control_dims (int) – the output dimension

  • layers (list[int]) – the sizes of the hidden layers

Return type:

Controller

Returns:

the controller

moptipyapps.dynamic_control.controllers.codegen module

A simple code generator.

class moptipyapps.dynamic_control.controllers.codegen.CodeGenerator(args='', retval='None', fastmath=True)[source]

Bases: object

A simple code generator.

build()[source]

Compile the generated code.

Return type:

Callable

Returns:

the generated function

endline()[source]

End a line.

Return type:

None

indent()[source]

Increase the indent.

Return type:

None

unindent()[source]

Increase the indent.

Return type:

None

write(text)[source]

Write some code.

Parameters:

text (str) – the code text

Return type:

None

writeln(text='')[source]

End a line.

Parameters:

text (str, default: '') – the text to be written

Return type:

None

moptipyapps.dynamic_control.controllers.cubic module

A cubic controller.

A cubic controller is a function where all value of the state vector enter the computation plainly, squared, and raised to the third power. The powers of their combinations do not exceed the third power, e.g., the controller is a linear combination of A, B, A², AB, B², A³, A²B, B²A, and B³ if the state has values A and B. The controller represents the multipliers for these coefficients.

moptipyapps.dynamic_control.controllers.cubic.cubic(system)[source]

Create a cubic controller for the given equations object.

Parameters:

system (System) – the equations object

Return type:

Controller

Returns:

the cubic controller

moptipyapps.dynamic_control.controllers.linear module

A linear controller.

In a linear controller, all values of the state vector enter only as-is, i.e., it is a linear combination of A and B if the state is composed of the two values A and B. We then optimize the weights of these coefficients.

moptipyapps.dynamic_control.controllers.linear.linear(system)[source]

Create a linear controller for the given equations object.

Parameters:

system (System) – the equations object

Return type:

Controller

Returns:

the linear controller

moptipyapps.dynamic_control.controllers.min_ann module

Poor man’s Artificial Neural Networks with minimized input.

ANNs that include the state as input variable together with an additional variable, say z. The controller output is then the value z* for which the ANN takes on the smallest value (under the current state). In other words, the ANN is supposed to model the system’s objective function. The idea is similar to ann, but instead of using the output of the ANNs as controller values, we use the value z* for which the output of the ANN becomes minimal as controller value.

moptipyapps.dynamic_control.controllers.min_ann.PHI: Final[float] = 1.618033988749895

the golden ratio

moptipyapps.dynamic_control.controllers.min_ann.min_anns(system)[source]

Create poor man’s ANNs for modeling the objective function.

Based on the dimensionality of the state space, we generate a set of ANNs with different numbers of layers and neurons. The weights of the neurons can then directly be optimized by a numerical optimization algorithm. This is, of course, probably much less efficient that doing some proper learning like back-propagation. However, it allows us to easily plug the ANNs into the same optimization routines as other controllers.

Parameters:

system (System) – the equations object

Return type:

Iterable[Controller]

Returns:

the ANNs

moptipyapps.dynamic_control.controllers.partially_linear module

Partially linear controllers.

Partially linear controllers are encoded as sets of linear controllers and anchor points. The anchors are coordinates in the state space. For each state, the linear controller with the closest anchor point is used. In other words, these controllers are basically choices among multiple linear controllers.

moptipyapps.dynamic_control.controllers.partially_linear.partially_linear(system)[source]

Create a several linear controllers for the given equations object.

Parameters:

system (System) – the equations object

Return type:

Iterable[Controller]

Returns:

the partially linear controllers

moptipyapps.dynamic_control.controllers.peaks module

Peak functions.

Instead of synthesizing ann with arctan as activation function, we here use exp(-a²) as activation function. This function does not represent a transition from -1 to 1, but a single peak around 0.

moptipyapps.dynamic_control.controllers.peaks.peaks(system)[source]

Create poor man’s PNNs fitting to a given system.

Based on the dimensionality of the state space, we generate a set of PNNs with different numbers of layers and neurons. The weights of the neurons can then directly be optimized by a numerical optimization algorithm. This is, of course, probably much less efficient that doing some proper learning like back-propagation. However, it allows us to easily plug the PNNs into the same optimization routines as other controllers.

Parameters:

system (System) – the equations object

Return type:

Iterable[Controller]

Returns:

the PNNs

moptipyapps.dynamic_control.controllers.predefined module

A set of pre-defined controllers.

In this module, we provide a set of pre-defined controllers taken from the works of NOACK, CORNEJO MACEDA, LI, and SUN of the Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)). We ignore the parameterizations offered in the original works and instead synthesize the parameter values by ourselves.

  1. Yuxiang LI (李宇翔). Jet Mixing Enhancement using Deep Reinforcement Learning (基于深度强化学习的射流混合增强控制). MSc Thesis. Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)). January 2023.

  2. Wei SUN (孙伟). Wake Control of 1-2-3 Fluidic Pinball using Deep Reinforcement Learning (基于深度强化学习方法的 1-2-3 流体弹球尾流控制). MSc Thesis. Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)). January 2023.

  3. Guy Yoslan CORNEJO MACEDA, François LUSSEYRAN, and Bernd R. NOACK. xMLC: A Toolkit for Machine Learning Control, First Edition. Machine Learning Tools in Fluid Mechanics, Vol 2. Shenzhen & Paris; Universitätsbibliothek der Technischen Universität Braunschweig. 2022 https://doi.org/10.24355/dbbs.084-202208220937-0

moptipyapps.dynamic_control.controllers.predefined.predefined(system)[source]

Create a set of pre-defined controllers for the given equations object.

Parameters:

system (System) – the equations object

Return type:

tuple[Controller, ...]

Returns:

the linear controller

moptipyapps.dynamic_control.controllers.quadratic module

A quadratic controller.

A quadratic controller is a function where all value of the state vector enter the computation plainly and squared. The powers of their combinations do not exceed two, e.g., the controller is a linear combination of A, B, A², AB, and B² if the state has values A and B. The controller represents the multipliers for these coefficients.

moptipyapps.dynamic_control.controllers.quadratic.quadratic(system)[source]

Create a quadratic controller for the given equations object.

Parameters:

system (System) – the equations object

Return type:

Controller

Returns:

the quadratic controller

diff --git a/moptipyapps.dynamic_control.html b/moptipyapps.dynamic_control.html new file mode 100644 index 00000000..d7de0b8e --- /dev/null +++ b/moptipyapps.dynamic_control.html @@ -0,0 +1,153 @@ +moptipyapps.dynamic_control package — moptipyapps 0.8.62 documentation

moptipyapps.dynamic_control package

Examples of dynamic control problems.

Here we have examples for dynamic control problems. An instance of the dynamic control problem is composed of

  • a system of differential equations that govern the state of a system and that also incorporate the output of a controller,

  • a controller blueprint, i.e., a function that can be parameterized and that computes a controller output from the system state, and

  • an objective that rates how well-behaved the system driven by the controller is in a simulation.

Such systems can serve as primitive testbeds of actual dynamic control scenarios such as flight control or other fluid dynamic systems. Different from such more complicated systems, they are much faster and easier to simulate, though. So we can play with them much more easily and quickly.

The initial starting point of the work here were conversations with Prof. Dr. Bernd NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)) as well as the following two MSc theses and book:

  1. Yuxiang LI (李宇翔). Jet Mixing Enhancement using Deep Reinforcement Learning (基于深度强化学习的射流混合增强控制). MSc Thesis. Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)). January 2023.

  2. Wei SUN (孙伟). Wake Control of 1-2-3 Fluidic Pinball using Deep Reinforcement Learning (基于深度强化学习方法的 1-2-3 流体弹球尾流控制). MSc Thesis. Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)). January 2023.

  3. Guy Yoslan CORNEJO MACEDA, François LUSSEYRAN, and Bernd R. NOACK. xMLC: A Toolkit for Machine Learning Control, First Edition. Machine Learning Tools in Fluid Mechanics, Vol 2. Shenzhen & Paris; Universitätsbibliothek der Technischen Universität Braunschweig. 2022 https://doi.org/10.24355/dbbs.084-202208220937-0

Subpackages

Submodules

moptipyapps.dynamic_control.controller module

A base class for implementing controllers.

A controller basically is a parameterizable function that receives the current state and time of a system as input and computes one or multiple controller values as output. These controller values are then used to influence how the state of the system changes in the next iteration. In the dynamic systems control optimization task, the goal is to find the right parameterization for the controller such that an objective is minimized.

Examples for different controllers for dynamic systems are given in package controllers.

class moptipyapps.dynamic_control.controller.Controller(name, state_dims, control_dims, param_dims, func=None)[source]

Bases: Component

A class for governing a system via differential equations.

control_dims: Final[int]

the dimensions of the controller output

controller(state, time, params, out)[source]

Compute the control value and store it in out.

Parameters:
  • state (ndarray) – the state vector

  • time (float) – the time value

  • params (ndarray) – the controller variables

  • out (ndarray) – the output array to receive the controller values

Return type:

None

log_parameters_to(logger)[source]

Log all parameters of this component as key-value pairs.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

name: Final[str]

the controller name

param_dims: Final[int]

the dimensions of the controller parameter

parameter_space()[source]

Create a vector space to represent the possible parameterizations.

Return type:

VectorSpace

Returns:

a vector space for the possible parameterizations of this controller.

state_dims: Final[int]

the dimensions of the state variable

moptipyapps.dynamic_control.experiment_raw module

An example experiment for dynamic control.

In this experiment, we try to synthesize (i.e., parameterize) controllers (controller) that steer a dynamic system (system) into a state by using a figure of merit (objective) which minimizes both the squared system state and controller effort.

A model-based experiment variant is given in experiment_surrogate.

moptipyapps.dynamic_control.experiment_raw.base_setup(instance)[source]

Create the basic setup.

Parameters:

instance (Instance) – the instance to use

Return type:

Execution

Returns:

the basic execution

moptipyapps.dynamic_control.experiment_raw.cmaes(instance)[source]

Create the Bi-Pop-CMA-ES setup.

Parameters:

instance (Instance) – the problem instance

Return type:

Execution

Returns:

the setup

moptipyapps.dynamic_control.experiment_raw.make_instances()[source]

Create the instances to be used in the dynamic control experiment.

Return type:

Iterable[Callable[[], Instance]]

Returns:

the instances to be used in the dynamic control experiment.

moptipyapps.dynamic_control.experiment_raw.on_completion(instance, log_file, process)[source]

Plot the corresponding figures and print the objective value components.

Parameters:
  • instance (Any) – the problem instance

  • log_file (Path) – the log file

  • process (Process) – the process

Return type:

None

moptipyapps.dynamic_control.experiment_raw.run(base_dir, n_runs=5)[source]

Run the experiment.

Parameters:
  • base_dir (str) – the base directory

  • n_runs (int, default: 5) – the number of runs

Return type:

None

moptipyapps.dynamic_control.experiment_surrogate module

An example experiment for dynamic control using surrogate system models.

In this experiment, we again try to synthesize (i.e., parameterize) controllers (controller) that steer a dynamic system (system) into a state by using a figure of merit (objective) which minimizes both the squared system state and controller effort.

The difference compared to experiment_raw is that we also try to synthesize a system model at the same time. We employ the procedure detailed in surrogate_optimizer for this purpose.

Word of advice: This experiment takes extremely long and needs a lot of memory!

The starting points of the work here were conversations with Prof. Dr. Bernd NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)).

moptipyapps.dynamic_control.experiment_surrogate.MAX_FES: Final[int] = 64

the total objective function evaluations

moptipyapps.dynamic_control.experiment_surrogate.base_setup(instance)[source]

Create the basic setup.

Parameters:

instance (Instance) – the instance to use

Return type:

tuple[Execution, FigureOfMerit, VectorSpace]

Returns:

the basic execution

moptipyapps.dynamic_control.experiment_surrogate.cmaes_raw(instance)[source]

Create the Bi-Pop-CMA-ES setup.

Parameters:

instance (Instance) – the problem instance

Return type:

Execution

Returns:

the setup

moptipyapps.dynamic_control.experiment_surrogate.cmaes_surrogate(instance, fes_for_warmup=16, fes_for_training=128, fes_per_model_run=128, fancy_logs=True)[source]

Create the Bi-Pop-CMA-ES setup.

Parameters:
  • instance (SystemModel) – the problem instance

  • fes_for_warmup (int, default: 16) – the FEs to be used for warmup

  • fes_for_training (int, default: 128) – the milliseconds for training

  • fes_per_model_run (int, default: 128) – the milliseconds per model run

  • fancy_logs (bool, default: True) – should we do fancy logging?

Return type:

Execution

Returns:

the setup

moptipyapps.dynamic_control.experiment_surrogate.make_instances()[source]

Create the instances to be used in the dynamic control experiment.

Return type:

Iterable[Callable[[], SystemModel]]

Returns:

the instances to be used in the dynamic control experiment.

moptipyapps.dynamic_control.experiment_surrogate.on_completion(instance, log_file, process)[source]

Plot the corresponding figures and print the objective value components.

Parameters:
  • instance (Any) – the problem instance

  • log_file (Path) – the log file

  • process (Process) – the process

Return type:

None

moptipyapps.dynamic_control.experiment_surrogate.run(base_dir, n_runs=64)[source]

Run the experiment.

Parameters:
  • base_dir (str) – the base directory

  • n_runs (int, default: 64) – the number of runs

Return type:

None

moptipyapps.dynamic_control.instance module

An instance of the dynamic control synthesis problem.

An instance of the dynamic control synthesis problem is comprised of two components: a system of differential equations governing how the state of a system changes over time and a controller that uses the current system state as input and computes a controller value as output that influences the state change. Notice that the controller here is a parametric function. The goal of the dynamic system control is to find the right parameterization of the controller such that an objective is minimized. The objective here usually has the goal to bring the dynamic system into a stable state while using as little controller “energy” as possible.

An instance of the simultaneous control and model synthesis problem is an instance of the class SystemModel, which is a subclass of Instance. It also adds a controller blueprint for modelling the systems response (state differential) based on the system state and controller output.

The starting point of the work here were conversations with Prof. Dr. Bernd NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)).

class moptipyapps.dynamic_control.instance.Instance(system, controller, name_base=None)[source]

Bases: Component

An instance of the dynamic control problem.

controller: Final[Controller]

the controller applied to the system

describe_parameterization(title, parameters, base_name, dest_dir)[source]

Describe the performance of a given system of system.

Parameters:
  • title (str | None) – the optional title

  • parameters (ndarray) – the controller parameters

  • base_name (str) – the base name of the file to produce

  • dest_dir (str) – the destination directory

Return type:

tuple[Path, ...]

Returns:

the paths of the generated files

log_parameters_to(logger)[source]

Log all parameters of this component as key-value pairs.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

name: Final[str]

the name of this instance

system: Final[System]

the system governing the dynamic system

moptipyapps.dynamic_control.model_objective module

An objective function that can be used to synthesize system models.

We consider the dynamic system to be a function D(s, c) = ds/dt, where

  • s is the state vector (e.g., two-dimensional for the Stuart-Landau system, see stuart_landau, and three-dimensional for the Lorenz system, see lorenz),

  • c is the control vector (one-dimensional in both cases), and

  • ds/dt is the state differential (again two respectively three-dimensional).

The FigureOfMerit objective function allows us to collect tuples of (s,c) and ds/dt vectors. So all we need to do is to train a model M that receives as input a vector x=(s,c) (where (s,c) be the concatenation of s and c) and fill as output a vector ds/dt.

The objective function in this module minimizes the root mean square error over the model-computed ds/dt vectors and the actual ds/dt vectors. The model objective function is used by the surrogate_optimizer algorithm.

class moptipyapps.dynamic_control.model_objective.ModelObjective(real, model)[source]

Bases: Objective

The objective for modeling.

This objective function works on sequences of tuples (s, c) of the system state s and controller output c as well as the corresponding ds/dt differentials. The goal is to train a model M(s, c, q) that will compute the ds/dt values reasonably accurately. The model is parameterized with some vector q, think of M being, e.g., an artificial neural network and q being its weight vector. To find good values of q, this objective here computes the squared differences between the values M(s, c, q) and the expected outputs ds/dt.

These squared differences are then either averaged directly or the expm1(mean(logp1(…)))) hack of the FigureOfMeritLE is used to alleviate the impact of large differentials, depending on which “real” (controller-synthesis) objective is passed into the constructor).

The tuples (s, c) and ds/dt are pulled from the real objective with the method begin(). The ds/dt rows are flattened for performance reasons.

begin()[source]

Begin a model optimization run.

This function pulls the training data from the actual controller-synthesis objective function, which is an instance of FigureOfMerit, via the method get_differentials() and allocates internal data structures accordingly.

Return type:

None

end()[source]

End a model optimization run and free the associated memory.

Return type:

None

evaluate(x)[source]

Evaluate a model parameterization.

Parameters:

x (ndarray) – the model parameterization

Return type:

float

Returns:

the objective value

log_parameters_to(logger)[source]

Log all parameters of this component as key-value pairs.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

lower_bound()[source]

Get the lower bound of the model objective, which is 0.

Return type:

float

Returns:

0.0

moptipyapps.dynamic_control.objective module

An objective functions for the dynamic control problem.

The dynamic control problem means that our system starts in a given state and we try to move it to a stable state by using control effort. Controllers are trained over several training states, for each of which we can compute a figure of merit.

We offer two different approaches for this:

  • FigureOfMerit computes the arithmetic mean z over the separate figures of merit J of the training cases.

  • FigureOfMeritLE tries to smooth out the impact of bad starting states by computing exp(mean[log(J + 1)]) - 1.

These objective functions also offer a way to collect the state+control and corresponding differential vectors.

class moptipyapps.dynamic_control.objective.FigureOfMerit(instance, supports_model_mode=False)[source]

Bases: Objective

A base class for figures of merit.

evaluate(x)[source]

Evaluate the parameterization of a controller.

Parameters:

x (ndarray) – the controller parameters

Return type:

float

Returns:

the figure of merit

get_differentials()[source]

Get the collected differentials.

If supports_model_mode was set to True in the creating of this objective function, then the system will gather tuples (s, c) and ds/dt when in raw mode (see set_raw()) and make them available here to train system models (see set_model()). Notice that gathering training data is a very memory intense process.

Return type:

tuple[ndarray, ndarray]

Returns:

the collected differentials

initialize()[source]

Initialize the objective for use.

Return type:

None

instance: Final[Instance]

the dynamic control instance

log_parameters_to(logger)[source]

Log all parameters of this component as key-value pairs.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

lower_bound()[source]

Get the lower bound of the figure of merit, which is 0.

Return type:

float

Returns:

0.0

set_model(equations)[source]

Set the model-driven mode for the evaluation.

In this modus, the internal system equations are replaced by the callable equations passed into this function and the data collection is stopped. The idea is that equations could be a model synthesized on the data gathered (see get_differentials()) and thus does not represent the actual dynamic system but a model thereof. We could synthesize a controller for this model and for this purpose would use the exactly same objective function – just instead of using the actual system equations, we use the system model. Of course, we then need to deactivate the data gathering mechanism (see again get_differentials()), because the data would then not be real system data. You can toggle back to the actual system using set_raw().

Parameters:

equations (Callable[[ndarray, float, ndarray, ndarray], None]) – the equations to be used instead of the actual system’s differential equations.

Return type:

None

set_raw()[source]

Let this objective work on the original system equations.

The objective function here can be used in two modi: a) based on the original systems model, as given in system, or b) on a learned model of the system. This function here toggles to the former mode, i.e., to the actual system mode. In this modus, training data for training the system model will be gathered if the objective function is configured to do so. In that case, you can toggle to model mode via set_model().

Return type:

None

sum_up_results(results)[source]

Compute the final objective value from several single J values.

When synthesizing controllers, we do not just apply them to a single simulation run. Instead, we use multiple training cases (see training_starting_states) and perform training_steps simulation steps on each of them. Each such training starting state will result in a single J value, which is the sum of squared state and control values. We now compute the end objective value from these different J values by using this function here.

This will destroy the contents of results.

Parameters:

results (ndarray) – the array of J values

Return type:

float

Returns:

the final result

class moptipyapps.dynamic_control.objective.FigureOfMeritLE(instance, supports_model_mode=False)[source]

Bases: FigureOfMerit

Compute a exp(mean(log(z+1)))-1 over the figures of merit z.

Different from FigureOfMerit, we compute the mean of log(z + 1) where z be the figures of merit of the single training cases. We then return exp(mean[log(z + 1)]) - 1 as final result. The goal is to reduce the impact of training cases that require more control effort.

If we solve the dynamic control problem for diverse training cases, then we may have some very easy cases, where the system just needs a small control impulse to move into a stable and cheap state. Others may have very far out and expensive starting states that require lots of control efforts to be corrected. If we simply average over all states, then these expensive states will dominate whatever good we are doing in the cheap states. Averaging over the log(J+1) reduces such impact. We then compute exp[…]-1 of the result as cosmetics to get back into the original range of the figure of merits.

sum_up_results(results)[source]

Compute the final objective value from several single J values.

For each training case, there is one basic figure of merit J and here we compute exp(mean[log(J + 1)]) - 1 over all of these values.

Parameters:

results (ndarray) – the array of J values

Return type:

float

Returns:

the final result

moptipyapps.dynamic_control.ode module

A primitive integrator for systems of ordinary differential equations.

Many dynamic systems can be modeled as systems of ordinary differential equations that govern their progress over time. Trying to find out in which state such systems are at a given point in time means to integrate these equations until that point in time (starting from a starting state).

What we want to play around with, however, is synthesizing controllers. In this case, the differential equations also merge the output of the controller with the current state. If the controller behaves inappropriately, this may make the system diverge, i.e., some of its state variables go to infinity over time or sometimes rather quickly.

Using ODE integrators that compute the system state at pre-defined time steps is thus cumbersome, as the system may have already exploded at these goal times. Therefore, we perform ODE integration in several steps. First, we try it the “normal” way. However, as soon as the system escapes the sane parameter range, we stop. We then use the last point where the system was stable and the first point where it escaped the reasonable range to estimate a new reasonable end time for the integration. We do this until we finally succeed.

Thus, we can simulate a well-behaved system over a long time and an ill-behaved system for a shorter time period. Neither system will diverge.

The following functions are provided:

  • run_ode() executes a set of differential system equations and controller equations and returns an array with the system state, controller output, and time at the different interpolation steps.

  • t_from_ode() returns the total time over which the result of run_ode() was simulated.

  • j_from_ode() returns a figure of merit, i.e., a weighted sum of (a part of) the system state and the controller output from the result of run_ode().

  • diff_from_ode() extracts state difference information from the result of run_ode().

class moptipyapps.dynamic_control.ode.T

the type variable for ODE controller parameters

alias of TypeVar(‘T’)

moptipyapps.dynamic_control.ode.diff_from_ode(ode, state_dim)[source]

Compute all the state+control vectors and the resulting differentials.

This function returns two matrices. Each row of both matrices corresponds to a time slot. Each row in the first matrix holds the state vector and the control vector (that was computed by the controller). The corresponding row in the second matrix then holds the state differential resulting from the control vector being applied in the differential equations that govern the system state change.

The idea is that this function basically provides the data that we would like to learn when training a surrogate model for a system: From the current state and the computed control vector, we want that our model can give us the resulting system differential. If we have such a model and it works reasonably well, then we could essentially plug this model into run_ode() instead of the original equations parameter.

What this function does to compute the differential is to basically “invert” the dynamic weighting done by run_ode(). run_ode() starts in a given starting state s. It then computes the control vector c as a function of s, i.e., c(s). Then, the equations of the dynamic system (see module system) to compute the state differential D=ds/dt as a function of c(s) and s, i.e., as something like D(s, c(s)). The next step would be to update the state, i.e., to set s=s+D(s, c(s)). Unfortunately, this can make s go to infinity. So run_ode() will compute a dynamic weight w and do s=s+w*D(s, c(s)), where w is chosen such that the state vector s does not grow unboundedly. While s and c(s) and w are stored in one row of the result matrix of run_ode(), s+w*D(s,c(s)) is stored as state s in the next row. So what this function here basically does is to subtract the old state from the next state and divide the result by w to get D(s, c(s)). s and c(s) are already available directly in the ODE result and w is not needed anymore.

We then get the rows s, c(s) and D(s, c(s)) in the first and second result matrix, respectively. This can then be used to train a system model as proposed in model system_model.

Parameters:
Return type:

tuple[ndarray, ndarray]

Returns:

a tuple of the state+control vectors and the resulting state differential vectors

>>> od = np.array([
+...     [0, 0, 0, 0, 0, 0],  # state 0,0,0; control 0,0; time 0
+...     [1, 2, 3, 4, 5, 1],  # state 1,2,3; control 4,5; time 1
+...     [2, 3, 4, 5, 6, 3],  # state 2,3,4; control 5,6; time 3
+...     [4, 6, 8, 7, 7, 7]])    # state 4,6,8; control 7,7, time 7
+>>> res = diff_from_ode(od, 3)
+>>> res[0]  # state and control vectors, time col and last row removed
+array([[0, 0, 0, 0, 0],
+       [1, 2, 3, 4, 5],
+       [2, 3, 4, 5, 6]])
+>>> res[1]  # (state[i + 1] - state[i]) / (time[i + 1] / time[i])
+array([[1.  , 2.  , 3.  ],
+       [0.5 , 0.5 , 0.5 ],
+       [0.5 , 0.75, 1.  ]])
+
moptipyapps.dynamic_control.ode.j_from_ode(ode, state_dim, use_state_dims=-1, gamma=0.1)[source]

Compute the original figure of merit from an ODE array.

The figure of merit is the sum of state variable squares plus 0.1 times the control variable squares. We disregard the state variable values of the starting states (because they are the same for all controllers on a given training case and because the control cannot influence them) and we also disregard the final state and final controller output (as there is no time slice associated with them, i.e., we only “arrive” in them but basically spent 0 time in them in our simulation).

Parameters:
  • ode (ndarray) – the array returned by the ODE function, i.e., run_ode()

  • state_dim (int) – the state dimension

  • use_state_dims (int, default: -1) – the dimension until which the state is used, -1 for using the complete state

  • gamma (float, default: 0.1) – the weight of the controller input

Return type:

float

Returns:

the figure of merit

>>> od = np.array([[1, 2, 3, 4, 0],
+...                [5, 6, 7, 8, 1],
+...                [9, 6, 4, 3, 3],
+...                [7, 4, 2, 1, 7]])
+>>> sta_dim = 3
+>>> print(f"{j_from_ode(od, sta_dim):.10f}")
+110.0000000000
+>>> print((1.6 + 12.8 + 98 + 72 + 50 + 3.6 + 64 + 144 + 324) / 7)
+110.0
+>>> sta_dim = 3
+>>> print(f"{j_from_ode(od, 3, 2, 0.5):.10f}")
+97.1428571429
+>>> print((8 + 64 + 72 + 50 + 18 + 144 + 324) / 7)
+97.14285714285714
+
moptipyapps.dynamic_control.ode.multi_run_ode(test_starting_states, training_starting_states, collector, equations, controller, parameters, controller_dim=1, test_steps=5000, test_time=50.0, training_steps=5000, training_time=50.0, use_state_dims=-1, gamma=0.1)[source]

Invoke run_ode() multiple times and pass the result to collector.

This function allows us to perform multiple runs of the differential equation simulator, using different starting points. It also allows us to distinguish training and test points and to assign them different numbers of steps. For each of them, run_ode() will be applied and the returned matrix is passed to the collector function.

Parameters:
  • test_starting_states (Iterable[ndarray]) – the iterable of test starting states

  • training_starting_states (Iterable[ndarray]) – the iterable of training starting states

  • collector (Union[Callable[[int, ndarray, float, float], None], Iterable[Callable[[int, ndarray, float, float], None]]]) – the destination to receive the results, in the form of index, ode array, j, and t.

  • equations (Callable[[ndarray, float, ndarray, ndarray], None]) – the differential system

  • controller (Callable[[ndarray, float, TypeVar(T), ndarray], None]) – the controller function

  • parameters (TypeVar(T)) – the controller parameters

  • controller_dim (int, default: 1) – the dimension of the controller result

  • test_steps (int, default: 5000) – the number of test steps to simulate

  • test_time (float, default: 50.0) – the time limit for tests

  • training_steps (int, default: 5000) – the number of training steps to simulate

  • training_time (float, default: 50.0) – the time limit for training

  • use_state_dims (int, default: -1) – the dimension until which the state is used, -1 for using the complete state

  • gamma (float, default: 0.1) – the weight of the controller input

Return type:

None

moptipyapps.dynamic_control.ode.run_ode(starting_state, equations, controller, parameters, controller_dim=1, steps=5000, max_time=50.0)[source]

Simulate a set of controlled differential system.

The system begins in the starting state stored in the vector starting_state. In each time step, first, the controller is invoked. It receives the current system state vector as input, the current time t, its parameters (parameters), and an output array to store its computed control values into. This array has dimension controller_dim, which usually is 1. Then, the function system will be called and receives the current state vector, the time step t, and the controller output as input, as well as an output array to store the result of the differential into. This output array has the same dimension as the state vector.

Now the run_ode function simulates such a system over steps time steps over the closed interval [0, max_time]. If both the system and the controller are well-behaved, then the output array will contain steps rows with the state, controller, and time information of each step. If the system diverges at some point in time but we can simulate it reasonably well before that, then we try to simulate steps, but on a shorter time frame. If even that fails, you will get a single row output with 1e100 as the controller value.

This function returns a matrix where each row corresponds to a simulated time step. Each row contains three components in a concatenated fashion: 1. the state vector, 2. the control vector, 3. the time value

Parameters:
Return type:

ndarray

Returns:

a matrix where each row represents a point in time, composed of the current state, the controller output, and the length of the time slice

If we simulate the flight of a projectile with our ODE execution, then both the flight time as well as the flight length are about 0.12% off from what the mathematical solution of the flight system prescribe. That’s actually not bad for a crude and fast integration method…

>>> v = 100.0
+>>> angle = np.deg2rad(45.0)
+>>> v_x = v * np.cos(angle)
+>>> print(f"{v_x:.10f}")
+70.7106781187
+>>> v_y = v * np.sin(angle)
+>>> print(f"{v_y:.10f}")
+70.7106781187
+>>> def projectile(position, ttime, ctrl, out):
+...     out[0] = 70.71067811865474
+...     out[1] = 70.71067811865474 - ttime * 9.80665
+>>> param = 0.0   # ignore
+>>> def contrl(position, ttime, params, dest):
+...     dest[0] = 0.0  #  controller that does nothing
+>>> strt = np.array([0.0, 1.0])
+>>> ode = run_ode(strt, projectile, contrl, param, 1, 10000)
+>>> print(len(ode))
+10000
+>>> time_of_flight = 2 * v_y / 9.80665
+>>> print(f"{time_of_flight:.10f}")
+14.4209649817
+>>> travel_distance_x = time_of_flight * v_x
+>>> print(f"{travel_distance_x:.10f}")
+1019.7162129779
+>>> idx = np.argwhere(ode[:, 1] <= 0.0)[0][0]
+>>> print(idx)
+2887
+>>> print(f"{ode[idx - 1, 0]:.10f}")
+1020.4571309653
+>>> print(f"{ode[idx, 0]:.10f}")
+1020.8107197148
+>>> print(f"{ode[idx - 1, -1]:.10f}")
+14.4314431443
+>>> print(f"{ode[idx, -1]:.10f}")
+14.4364436444
+>>> print(ode[-1, -1])
+50.0
+
>>> def contrl2(position, ttime, params, dest):
+...     dest[0] = 1e50  #  controller that is ill-behaved
+>>> run_ode(strt, projectile, contrl2, param, 1, 10000)
+array([[0.e+000, 1.e+000, 1.e+100, 0.e+000]])
+
>>> def contrl3(position, ttime, params, dest):
+...     dest[0] = 1e50 if ttime > 10 else 0.0  # diverging controller
+>>> ode = run_ode(strt, projectile, contrl3, param, 1, 10000)
+>>> print(len(ode))
+10000
+>>> print(ode[-1])
+[690.10677249 224.06765771   0.           9.75958357]
+
>>> def projectile2(position, ttime, ctrl, out):
+...     out[:] = 0
+>>> ode = run_ode(strt, projectile2, contrl, param, 1, 10000)
+>>> print(len(ode))
+10000
+>>> print(ode[-1])
+[ 0.  1.  0. 50.]
+>>> ode = run_ode(strt, projectile2, contrl3, param, 1, 10000)
+>>> print(len(ode))
+10000
+>>> print(ode[-1])
+[0.         1.         0.         9.41234557]
+
moptipyapps.dynamic_control.ode.t_from_ode(ode)[source]

Get the time sum from an ODE solution.

The total time that we simulate a system depends on the behavior of the system.

Parameters:

ode (ndarray) – the ODE solution, as return from run_ode().

Return type:

float

Returns:

the total consumed time

>>> od = np.array([[1, 2, 3, 4, 0.1],
+...                [5, 6, 7, 8, 0.2],
+...                [9, 6, 4, 3, 0.3],
+...                [7, 4, 2, 1, 0.4]])
+>>> print(t_from_ode(od))
+0.4
+

moptipyapps.dynamic_control.results_log module

A logger for results gathered from ODE integration to a text file.

class moptipyapps.dynamic_control.results_log.ResultsLog(state_dim, out)[source]

Bases: AbstractContextManager

A class for logging results via multi_run_ode.

Function moptipyapps.dynamic_control.ode.multi_run_ode() can pass its results to various output generating procedures. This class here offers a procedure for writing them to a log file.

>>> def projectile(position, ttime, ctrl, out):
+...     out[0] = 70.71067811865474
+...     out[1] = 70.71067811865474 - ttime * 9.80665
+>>> param: np.ndarray = np.array([1])   # ignore
+>>> def contrl(position, ttime, params, dest):
+...     dest[0] = params[0]  #  controller that returns param
+>>> from io import StringIO
+>>> from moptipyapps.dynamic_control.ode import multi_run_ode
+>>> with StringIO() as sio:
+...     with ResultsLog(2, sio) as log:
+...         multi_run_ode([np.array([0.0, 1.0])],
+...                       [np.array([1.0, 1.0])],
+...                       log.collector, projectile, contrl, param,
+...                       1, 10000, 14.890, 10000, 14.961)
+...         x=sio.getvalue()
+>>> tt = x.split()
+>>> print(tt[0])
+figureOfMerit;totalTime;nSteps;start0;start1;end0;end1
+>>> for y in tt[1:]:
+...     print(";".join(f"{float(v):.3f}" for v in y.split(";")))
+403374.924;14.890;10000.000;0.000;1.000;1052.882;-33.244
+407810.750;14.961;10000.000;1.000;1.000;1058.902;-38.616
+
collector(index, ode, j, time)[source]

Log the result of a multi-ode run.

Parameters:
  • index (int) – the index of the result

  • ode (ndarray) – the ode result matrix

  • j (float) – the figure of merit

  • time (float) – the time value

Return type:

None

moptipyapps.dynamic_control.results_plot module

An illustrator for results gathered from ODE integration to multi-plots.

This evaluator will create figures where each sub-plot corresponds to one evolution of the system over time. The starting state is marked with a blue cross, the final state with a red one. Both states are connected with a line that marks the progress over time. The line begins with blue color and changes its color over violet/pinkish towards yellow the farther in time the system progresses.

moptipyapps.dynamic_control.results_plot.END_COLOR: Final[str] = 'red'

the end point color

moptipyapps.dynamic_control.results_plot.END_MARKER: Final[str] = 'o●'

the end marker

class moptipyapps.dynamic_control.results_plot.ResultsPlot(dest_file, sup_title, state_dims, plot_indices=(0,), state_dim_mod=0)[source]

Bases: AbstractContextManager

A class for plotting results via multi_run_ode.

Function moptipyapps.dynamic_control.ode.multi_run_ode() can pass its results to various output generating procedures. This class here offers a procedure for plotting them to a file.

collector(index, ode, j, time)[source]

Plot the result of a multi-ode run.

Parameters:
  • index (int) – the index of the result

  • ode (ndarray) – the ode result matrix

  • j (float) – the figure of merit

  • time (float) – the time value

Return type:

None

moptipyapps.dynamic_control.results_plot.START_COLOR: Final[str] = 'blue'

the start point color

moptipyapps.dynamic_control.results_plot.START_MARKER: Final[str] = '++'

the start marker

moptipyapps.dynamic_control.starting_points module

Synthesize some interesting starting points.

Here we have some basic functionality to synthesize starting points, i.e., training cases, for the dynamic systems control task. The points synthesized here by function make_interesting_starting_points() try to fulfill two goals:

  1. the points should be as far away from each other as possible in the state space,

  2. there should be points of many different distances from the state space origin, and

  3. compared to the coordinates of the other points, the coordinates of the synthesized points should be sometimes smaller and sometimes larger (where sometimes should ideally mean “equally often”).

These two goals are slightly contradicting and are achieved by forcing the points to be located on rings of increasing distance from the origin via interesting_point_transform() while maximizing their mean distance to each other via interesting_point_objective(). Since make_interesting_starting_points() is a bit slow, it makes sense to pre-compute the points and then store them as array constants.

moptipyapps.dynamic_control.starting_points.interesting_point_objective(x, other, max_radius, dim)[source]

Compute the point diversity. :rtype: float

  1. the distance between the points

  2. the distance to the other points

  3. the coordinates in the different dimensions should be equally often smaller and larger than those of the starting points

moptipyapps.dynamic_control.starting_points.interesting_point_transform(x, max_radius, dim)[source]

Transform interesting points.

Return type:

ndarray

>>> xx = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12.0])
+>>> ppp = interesting_point_transform(xx, 10.0, 3)
+>>> print(ppp)
+[[0.6681531  1.33630621 2.00445931]
+ [2.27921153 2.84901441 3.41881729]
+ [3.76928033 4.30774895 4.84621757]
+ [5.23423923 5.75766315 6.28108707]]
+>>> print(xx)
+[0.6681531  1.33630621 2.00445931 2.27921153 2.84901441 3.41881729
+ 3.76928033 4.30774895 4.84621757 5.23423923 5.75766315 6.28108707]
+>>> print([np.sqrt(np.square(pppp).sum()) for pppp in ppp])
+[2.5, 5.0, 7.5, 10.0]
+>>> ppp = interesting_point_transform(xx, 10.0, 3)
+>>> print(ppp)
+[[0.6681531  1.33630621 2.00445931]
+ [2.27921153 2.84901441 3.41881729]
+ [3.76928033 4.30774895 4.84621757]
+ [5.23423923 5.75766315 6.28108707]]
+>>> print([np.sqrt(np.square(pppp).sum()) for pppp in ppp])
+[2.5, 5.0, 7.5, 10.0]
+>>> ppp = interesting_point_transform(xx, 10.0, 2)
+>>> print(ppp)
+[[0.74535599 1.49071198]
+ [2.20132119 2.50305736]
+ [3.200922   3.8411064 ]
+ [4.39003072 5.01717796]
+ [5.66154475 6.11484713]
+ [6.75724629 7.3715414 ]]
+
moptipyapps.dynamic_control.starting_points.make_interesting_starting_points(n, other, log=True)[source]

Create some reasonably diverse starting points.

Parameters:
  • n (int) – the number of starting points

  • other (Iterable[Iterable[float]]) – the other points

  • log (bool, default: True) – write log output

Return type:

ndarray

Returns:

the starting points

>>> p = make_interesting_starting_points(
+...     3, np.array([[1.0, 2.0], [3.0, 2.9]]), False)
+>>> print(",".join(";".join(f"{x:.5f}" for x in row) for row in p))
+1.77767;0.33029,1.10482;3.44328,3.18515;-4.39064
+
>>> p = make_interesting_starting_points(
+...     3, np.array([[1.0, 2.0, 7.0], [3.0, 2.9, 1.1]]), False)
+>>> print(",".join(";".join(f"{x:.5f}" for x in row) for row in p))
+1.51392;-2.66567;0.86153,3.26154;-5.07757;2.03484,3.75627;5.88214;6.52310
+

moptipyapps.dynamic_control.surrogate_optimizer module

A surrogate system model-based Optimization approach.

In the real world, we want to synthesize a controller c(s, p) that can drive a dynamic system into a good state. The controller receives as input the current state s, say, from sensor readings. It can also be parameterized by a vector p, imagine c to be, for example, an artificial neural network and then p would be its weight vector. The output of c will influence the system in some way. In our example experiments, this is done by becoming part of the state differential ds/dt. Anyway, in the real world, the controller may steer the rotation speed of a rotor or the angles of rotor blades or whatever. Now you can imagine that doing real-world experiments is costly and takes a long time. Everytime we want to test a parameterization p, some form experiment, maybe in a wind tunnel, has to be done.

So it would be beneficial if we could replace the actual experiment by a simulation. This would mean that we learn a model M that can compute the state change ds/dt based on the current state s and controller output c at reasonable accuracy. If we had such a computational model, then we could run the controller optimization process on that model. Once finished, we could apply and evaluate the best controller that we have discovered in a real experiment. With the observed behavior of the actual controller system, we may even be able to update and improve our system model to become more accurate. So we could alternate between real experiments and optimization runs on the simulation. Of course, we would always need to do some real experiments at first to gather the data to obtain our initial model M. But if we can get this to work, then we could probably get much better controllers with fewer actual experiments.

This here is an algorithm that tries to implement the above pattern. This algorithm employs three different sub-algorithms:

  1. For sampling the initial fes_for_warmup FEs, it uses a warm-up algorithm - by default, this is done by random_sampling.

  2. Then, it spends fes_for_training steps on the collected data to train the model using the model training algorithm, which, by default, is BiPopCMAES.

  3. Then, it spends fes_per_model_run steps to train controllers on the model, by default again with a BiPopCMAES.

For the model and controller optimization, it thus uses by default the BiPop-CMA-ES offered by moptipy (BiPopCMAES). But it uses two instances of this algorithm, namely one to optimize the controller parameters and one that optimizes the model parameters. And, as said, a random sampling method to gather the initial samples.

The idea is that we divide the computational budget into a warmup and a model-based phase. In the warmup phase, we use CMA-ES to normally optimize the controller based on the actual simulation of the dynamic system. However, while doing so, for every time step in the simulation, we collect three things: The current state vector s, the control vector c, and the resulting state differential ds/dt. Now if we have such data, we can look at the dynamic system as a function D(s, c) = ds/dt. If we consider the dynamic system to be such a function and we have collected the vectors (s, c) and ds/dt, then we may as well attempt to learn this system. So after the warmup phase, our algorithm does the following: In a loop (for the rest of the computational budget), it first tries to learn a model M of D. Then, it replaces the actual differential equations of the system in ODE solution approach of the objective function with M. In other words, we kick out the actual system and instead use the learned system model M. We replace the differential equations that describe the system using M. We can now run an entire optimization process on this learned model only, with ODE integration and all. This optimization process gives us one new solution which we then evaluate on the real objective function (which costs 1 FE and gives us a new heap of (s, c) and ds/dt vectors). With this new data, we again learn a new and hopefully more accurate model M. This process is iterated until the rest of the computational budget is exhausted.

This approach hopefully allows us to learn a model of a dynamic system while synthesizing a controller for it. Since we can have infinitely more time to synthesize the controller on a learned system model compared to an actual model, this may give us much better results.

The starting points of the work here were conversations with Prof. Dr. Bernd NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)).

class moptipyapps.dynamic_control.surrogate_optimizer.SurrogateOptimizer(system_model, controller_space, objective, fes_for_warmup, fes_for_training=None, ms_for_training=None, fes_per_model_run=None, ms_per_model_run=None, fancy_logs=False, warmup_algorithm=<function SurrogateOptimizer.<lambda>>, model_training_algorithm=<function _bpcmaes>, controller_training_algorithm=<function _bpcmaes>)[source]

Bases: Algorithm

A surrogate model-based CMA-ES algorithm.

fancy_logs: Final[bool]

should we do fancy logging?

fes_for_training: Final[int | None]

the FEs for training the model

fes_for_warmup: Final[int]

the number of objective function evaluations to be used for warmup

fes_per_model_run: Final[int | None]

the FEs for each run on the model

initialize()[source]

Initialize.

Return type:

None

log_parameters_to(logger)[source]

Log all parameters of this component as key-value pairs.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

ms_for_training: Final[int | None]

the ms for training the model

ms_per_model_run: Final[int | None]

the ms for each run on the model

solve(process)[source]

Solve the modelling problem.

This function begins by spending fes_for_warmup objective function evaluations (FEs) on the actual problem, i.e., by trying to synthesize controllers for the “real” system, using process to evaluate the controller performance. The objective function passed to the constructor (an instance of FigureOfMerit) must be used by process as well. This way, during the warm-up phase, we can collect tuples of the (system state, controller output) and the resulting system differential for each simulated time step. This is done via set_raw().

After the warm-up phase, we can obtain these collected data via get_differentials(). The data is then used to train a model via the model objective function model_objective. The system model is again basically a Controller which is parameterized appropriately. For this, we use a CMA-ES algorithm for fes_for_training FEs.

Once the model training is completed, we switch the objective function to use the model instead of the actual system for evaluating controllers, which is done via set_model(). We then train a completely new controller on the model objective function. Notice that now, the actual system is not involved at all. We do this again using a CMA-ES algorithm for fes_per_model_run FEs.

After training the controller, we can evaluate it on the real system using the evaluate() method of the actual process (after switching back to the real model via set_raw()). This nets us a) the actual controller performance and b) a new set of (system state, controller output) + system state differential tuples.

Since we now have more data, we can go back and train a new system model and then use this model for another model-based optimization run. And so on, and so on. Until the budget is exhausted.

Parameters:

process (Process) – the original optimization process, which must use the objective function (an instance of FigureOfMerit) as its objective function.

Return type:

None

system_model: Final[SystemModel]

the system model

moptipyapps.dynamic_control.system module

A class to model a dynamic system governed by differential equations.

A system has a current state vector at any point in time. The state changes over time based on differential equations. These equations can be influenced by the output of a controller. Our System presents the state dimension and differential equations. It also presents several starting states for simulating the system. The starting states are divided into training and testing states. The training states can be used for, well, training controllers to learn how to handle the system. The testing states are then used to verify whether a synthesized controller actually works.

Examples for different dynamic systems are given in package systems.

class moptipyapps.dynamic_control.system.System(name, state_dims, control_dims, state_dim_mod, state_dims_in_j, gamma, test_starting_states, training_starting_states, test_steps=5000, test_time=50.0, training_steps=1000, training_time=50.0, plot_examples=(0,))[source]

Bases: Component

A class for governing a system via differential system.

control_dims: Final[int]

the dimensions of the controller output

describe_system(title, controller, parameters, base_name, dest_dir, skip_if_exists=False)[source]

Describe the performance of a given system of system.

Parameters:
  • title (str | None) – the title

  • controller (Callable[[ndarray, float, TypeVar(T), ndarray], None]) – the controller to simulate

  • parameters (TypeVar(T)) – the controller parameters

  • base_name (str) – the base name of the file to produce

  • dest_dir (str) – the destination directory

  • skip_if_exists (bool, default: False) – if the file already exists

Return type:

tuple[Path, ...]

Returns:

the paths of the generated files

describe_system_without_control(dest_dir, skip_if_exists=True)[source]

Describe the performance of a given system of system.

Parameters:
  • dest_dir (str) – the destination directory

  • skip_if_exists (bool, default: True) – if the file already exists

Return type:

tuple[Path, ...]

Returns:

the paths of the generated files

equations(state, time, control, out)[source]

Compute the values of the differential equations to be simulated.

Parameters:
  • state (ndarray) – the state of the system

  • time (float) – the time index

  • control (ndarray) – the output of the controller

  • out (ndarray) – the differential, i.e., the output of this function

Return type:

None

gamma: Final[float]

The Weight of the control values in the figure of merit computation.

log_parameters_to(logger)[source]

Log all parameters of this component as key-value pairs.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

name: Final[str]

the name of the model

plot_examples: Final[tuple[int, ...]]

the plot examples

plot_points(dest_dir, skip_if_exists=True)[source]

Plot the training and testing points of the equation.

Parameters:
  • dest_dir (str) – the destination directory

  • skip_if_exists (bool, default: True) – True to skip existing files, False otherwise

Return type:

Path

Returns:

the plotted file

state_dim_mod: Final[int]

The modulus for the state dimensions for plotting

state_dims: Final[int]

the dimensions of the state variable

state_dims_in_j: Final[int]

The number of dimensions used in the J computation

test_starting_states: Final[ndarray]

the test starting states

test_steps: Final[int]

the test simulation steps

test_time: Final[float]

the test time

training_starting_states: Final[ndarray]

the test starting states

training_steps: Final[int]

the training simulation steps

training_time: Final[float]

the training time

class moptipyapps.dynamic_control.system.T

the type variable for ODE controller parameters

alias of TypeVar(‘T’)

moptipyapps.dynamic_control.system_model module

An extended dynamic control problem Instance with a model for the dynamics.

An Instance is a combination of

  • a set of differential equations (system) that govern the change D=ds/dt of the state s of a dynamic system (based on its current state s and the controller output c(s)), i.e., ds/dt=D(s,c(s)) and

  • a blueprint c(s, p) of the controller c(s) (controller).

The blueprint of the controller is basically a function that can be parameterized, so it is actually a function c(s, p) and the goal of optimization is to parameterize it in such a way that the figure of merit, i.e., the objective value (objective) of the system (usually the sum of squared state values and squared controller outputs) is minimized. Parameterization means finding good values p such that the above goal is reached. In other words, we want to synthesize a controller (by finding good values of p) in such a way that the state equations drive the system into a stable state, usually ideally the origin of the coordinate system.

Now here this Instance is extended to a SystemModel by adding a parameterized model M(s, c(s), q) to the mix. The idea is to develop the parameterization q of the model M that can replace the actual system equations D. Such a model receives as input the current system state vector s and the controller output c(s) for the state vector s. It will return the differential D of the system state, i.e., ds/dt. In other words, a properly constructed model can replace the equations parameter in the ODE integrator run_ode(). The input used for training is provided by diff_from_ode().

What we do here is to re-use the same function models as used in controllers (controller) and learn their parameterizations from the observed data. If successful, we can wrap everything together into a Callable and plug it into the system instead of the original equations.

The thing that SystemModel offers is thus a blueprint of the model M. Obviously, we can conceive many different such blueprints. We could have a linear model, a quadratic model, or maybe neural network (in which case, we need to decide about the number of layers and layer sizes). So an instance of this surrogate model based approach has an equation system, a controller blueprint, and a model blueprint.

An example implementation of the concept of synthesizing models for a dynamic system in order to synthesize controllers is given as the surrogate_optimizer algorithm. Examples for different dynamic systems controllers (which we here also use to model the systems themselves) are given in package controllers.

The starting points of the work here were conversations with Prof. Dr. Bernd NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)).

class moptipyapps.dynamic_control.system_model.SystemModel(system, controller, model)[source]

Bases: Instance

A dynamic system control Instance with a system model blueprint.

log_parameters_to(logger)[source]

Log all parameters of this component as key-value pairs.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

model: Final[Controller]

the model blueprint that can be trained to hopefully replace the system in the ODE integration / system simulation procedure

diff --git a/moptipyapps.dynamic_control.systems.html b/moptipyapps.dynamic_control.systems.html new file mode 100644 index 00000000..17a19c94 --- /dev/null +++ b/moptipyapps.dynamic_control.systems.html @@ -0,0 +1 @@ +moptipyapps.dynamic_control.systems package — moptipyapps 0.8.62 documentation

moptipyapps.dynamic_control.systems package

Examples for differential equations systems with dynamic control.

Here we provide several systems whose state is governed by differential equations that allow us to plug in a controller. The controller receives the state of the system as input and its output is added to one (or more) of the differential equations.

  • The stuart_landau system has a two-dimensional state space. The goal is to navigate the system into the origin. Without control, the system will converge to oscillate in a circle around the origin with a diameter of sqrt(0.1).

  • The lorenz system has a three-dimensional state space. The goal is again to move the system to the origin using a control strategy (and to keep it there). Without control, it will converge to a double-circle oscillation.

Submodules

moptipyapps.dynamic_control.systems.lorenz module

The three-dimensional Lorenz system.

The initial starting point of the work here were conversations with Prof. Dr. Bernd NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)) as well as the following two MSc theses and book:

  1. Yuxiang LI (李宇翔). Jet Mixing Enhancement using Deep Reinforcement Learning (基于深度强化学习的射流混合增强控制). MSc Thesis. Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)). January 2023.

  2. Wei SUN (孙伟). Wake Control of 1-2-3 Fluidic Pinball using Deep Reinforcement Learning (基于深度强化学习方法的 1-2-3 流体弹球尾流控制). MSc Thesis. Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)). January 2023.

  3. Guy Yoslan CORNEJO MACEDA, François LUSSEYRAN, and Bernd R. NOACK. xMLC: A Toolkit for Machine Learning Control, First Edition. Machine Learning Tools in Fluid Mechanics, Vol 2. Shenzhen & Paris; Universitätsbibliothek der Technischen Universität Braunschweig. 2022 https://doi.org/10.24355/dbbs.084-202208220937-0

moptipyapps.dynamic_control.systems.lorenz.LORENZ_111: Final[System] = lorenz

The Lorenz system with 111 training points.

moptipyapps.dynamic_control.systems.lorenz.LORENZ_4: Final[System] = lorenz

The Lorenz system with 4 training points.

moptipyapps.dynamic_control.systems.lorenz.make_lorenz(n_points)[source]

Create the Lorenz system.

Parameters:

n_points (int) – the number of training points

Return type:

System

Returns:

the Lorenz system

moptipyapps.dynamic_control.systems.stuart_landau module

The two-dimensional Stuart-Landau system.

The initial starting point of the work here were conversations with Prof. Dr. Bernd NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)) as well as the following two MSc theses and book:

  1. Yuxiang LI (李宇翔). Jet Mixing Enhancement using Deep Reinforcement Learning (基于深度强化学习的射流混合增强控制). MSc Thesis. Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)). January 2023.

  2. Wei SUN (孙伟). Wake Control of 1-2-3 Fluidic Pinball using Deep Reinforcement Learning (基于深度强化学习方法的 1-2-3 流体弹球尾流控制). MSc Thesis. Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)). January 2023.

  3. Guy Yoslan CORNEJO MACEDA, François LUSSEYRAN, and Bernd R. NOACK. xMLC: A Toolkit for Machine Learning Control, First Edition. Machine Learning Tools in Fluid Mechanics, Vol 2. Shenzhen & Paris; Universitätsbibliothek der Technischen Universität Braunschweig. 2022 https://doi.org/10.24355/dbbs.084-202208220937-0

moptipyapps.dynamic_control.systems.stuart_landau.STUART_LANDAU_111: Final[System] = stuart_landau

The Stuart-Landau system with 111 training points.

moptipyapps.dynamic_control.systems.stuart_landau.STUART_LANDAU_4: Final[System] = stuart_landau

The Stuart-Landau system with 4 training points.

moptipyapps.dynamic_control.systems.stuart_landau.make_stuart_landau(n_points)[source]

Create the Stuart-Landau system.

Parameters:

n_points (int) – the number of training points

Return type:

System

Returns:

the Stuart-Landau system

moptipyapps.dynamic_control.systems.three_coupled_oscillators module

A system of three coupled oscillators.

There are three oscillators located in a two-dimensional plane. Thus, there are a total of six coordinates.

The initial starting point of the work here were conversations with Prof. Dr. Bernd NOACK and Guy Yoslan CORNEJO MACEDA of the Harbin Institute of Technology in Shenzhen, China (哈尔滨工业大学(深圳)) as well as the following paper:

  1. Ruiying Li, Bernd R. Noack, Laurent Cordier, Jacques Borée, Eurika Kaiser, and Fabien Harambat. Linear genetic programming control for strongly nonlinear dynamics with frequency crosstalk. Archives of Mechanics. 70(6):505-534. Warszawa 2018. Seventy Years of the Archives of Mechanics. https://doi.org/10.24423/aom.3000. Also: arXiv:1705.00367v1 [physics.flu-dyn] 30 Apr 2017. https://arxiv.org/abs/1705.00367.

moptipyapps.dynamic_control.systems.three_coupled_oscillators.THREE_COUPLED_OSCILLATORS: Final[System] = 3oscillators

The 3 oscillators system with 4 training points.

moptipyapps.dynamic_control.systems.three_coupled_oscillators.make_3_couple_oscillators(n_points)[source]

Create the oscillator system.

Parameters:

n_points (int) – the number of training points

Return type:

System

Returns:

the Lorenz system

diff --git a/moptipyapps.html b/moptipyapps.html new file mode 100644 index 00000000..a86fb615 --- /dev/null +++ b/moptipyapps.html @@ -0,0 +1,16 @@ +moptipyapps package — moptipyapps 0.8.62 documentation

moptipyapps package

Applications of Metaheuristic Optimization in Python.

Currently, the following applications are implemented:

  • binpacking2d provides methods to solve two-dimensional bin packing instances,

  • dynamic_control can simulate controlled dynamic systems of differential equations and our goal is to synthesize controllers that can steer such systems into beneficial states,

  • mod:~moptipyapps.tsp offers instances of the well-known Traveling Salesperson Problem (TSP) and some very basic algorithms to tackle it.

The following additional tools are implemented:

  • tests offers unit tests to try out optimization algorithms and other instances of component on the different problems that are provided above.

  • shared offers shared constants and tools.

Subpackages

Submodules

moptipyapps.shared module

Some shared variables and constants.

moptipyapps.shared.SCOPE_INSTANCE: Final[str] = 'inst'

the instance scope

moptipyapps.shared.moptipyapps_argparser(file, description, epilog)[source]

Create an argument parser with default settings.

Parameters:
  • file (str) – the __file__ special variable of the calling script

  • description (str) – the description string

  • epilog (str) – the epilogue string

Return type:

ArgumentParser

Returns:

the argument parser

>>> ap = moptipyapps_argparser(
+...     __file__, "This is a test program.", "This is a test.")
+>>> isinstance(ap, argparse.ArgumentParser)
+True
+>>> "Copyright" in ap.epilog
+True
+

Print the standard csv footer for moptipyapps.

Parameters:
  • _ – the setup object, ignored

  • additional (str | None, default: None) – any additional output string

Return type:

Iterable[str]

Returns:

the comments

>>> for s in motipyapps_footer_bottom_comments(None, "bla"):
+...     print(s[:49])
+This data has been generated with moptipyapps ver
+bla
+You can find moptipyapps at https://thomasweise.g
+
>>> for s in motipyapps_footer_bottom_comments(None, None):
+...     print(s[:49])
+This data has been generated with moptipyapps ver
+You can find moptipyapps at https://thomasweise.g
+

moptipyapps.version module

An internal file with the version of the moptipyapps package.

diff --git a/moptipyapps.order1d.html b/moptipyapps.order1d.html new file mode 100644 index 00000000..1115aa9f --- /dev/null +++ b/moptipyapps.order1d.html @@ -0,0 +1,211 @@ +moptipyapps.order1d package — moptipyapps 0.8.62 documentation

moptipyapps.order1d package

A set of tools for ordering objects in 1 dimension.

Let’s assume that we have n objects and a distance metric that can compute the distance between two objects. We do not know and also do not care about in how many dimension the objects exist - we just have objects and a distance metric.

Now we want to find a one-dimensional order of the objects that reflects their original distance-based topology. For each object a, we want that its closest neighbor in the order is also its actual closest neighbor according to the distance metric. It’s second-closest neighbor should be the actual second-closest neighbor according to the distance metric. And so on.

Since we only care about the object order and do not want to metrically map the distances to one dimension, we can represent the solution as permutation of natural numbers.

Of course, in a one-dimensional order, each object has exactly two closest neighbors (the one on its left and the one on its right) unless it is situated either at the beginning or end of the order, in which case it has exactly one closest neighbor. Based on the actual distance metric, an object may have any number of closest neighbors, maybe only one, or maybe three equally-far away objects. So it is not clear whether a perfect mapping to the one-dimensional permutations even exists. Probably it will not, but this shall not bother us.

Either way, we can try to find one that comes as close as possible to the real deal.

Another way to describe this problem is as follows: Imagine that you have n objects and only know their mutual distances. You want to arrange them on a one-dimensional axis in a way that does sort of reflect their neighborhood structure in whatever space they are originally located in.

The goal of solving this one-dimensional ordering problem is then to arrange n objects on a 1-dimensional (e.g., horizontal) axis given a distance matrix describing their location in a potentially high-dimensional or unstructured space. The objects should be arranged in such a way that, for each object,

  • the nearest neighbors on the 1-dimensional axis are also the nearest neighbors in the original space (according to the distance matrix provided),

  • the second nearest neighbors on the 1-dimensional axis are also the second nearest neighbors in the original space (according to the distance matrix provided),

  • the third nearest neighbors on the 1-dimensional axis are also the third nearest neighbors in the original space (according to the distance matrix provided),

  • and so on; with (e.g., quadratically) decreasing weights of neighbor distance ranks.

We do not care about the actual precise distances (e.g., something like “0.001”) between the objects on either the one-dimensional nor the original space. We only care about the distance ranks, i.e., about “2nd nearest neighbor,” but not “0.012 distance units away.” The solutions of this problem are thus permutations (orders) of the objects. Of course, if we really want to plot the objects, such a permutation can easily be translated to x-coordinates, say, by dividing the index of an object by the number of objects, which nets values in [0,1]. But basically, we reduce the task to finding permutations of objects that reflect the neighbor structure of the original space as closely as possible.

If such a problem is solved correctly, then the arrangement on the one-dimensional axis should properly reflect the arrangement of the objects in the original space. Of course, solving this problem exactly may not actually be possible, since an object on a one-dimensional axis may either have exactly two i-nearest-neighbors (if it is at least i slots away from either end of the permutation) or exactly 1 such neighbor, if it is closer that i units. The object directly at the start of the permutation has only 1 nearest neighbor (the object that comes next). That next object, however, has two, namely the first object and the third object. In the original space where the objects come from, however, there may be any number of “nearest neighbors.” Imagine a two-dimensional space where one object sits at the center of a circle of other objects. Then all other objects are its nearest neighbors, whereas an object on the circle either has exactly two nearest neighbors or, maybe, in the odd situation that the radius equals a multiple of the distance to the neighbors on the circle, three. Such a structure cannot be represented exactly in one dimension.

But that’s OK. Because we mainly do this for visualization purposes anyway.

Now here comes the cool thing: We can cast this problem to a Quadratic Assignment Problem (qap). A QAP is defined by a distance matrix and a flow matrix. The idea is to assign n facilities to locations. The distances between the locations are given by the distance matrix. At the same time, the flow matrix defines the flows between the facilities. The goal is to find the assignment of facilities to locations that minimizes the overall “flow times distance” product sum.

We can translate the “One-Dimensional Ordering Problem” to the QAP as follows: Imagine that we have n unique objects and know the (symmetric) distances between them.

Let’s say that we have four numbers as objects, 1, 2, 3, 4. >>> data = [1, 2, 3, 4] >>> n = len(data)

The distance between two numbers a and b be abs(a - b). >>> def dist(a, b): … return abs(a - b)

The original distance matrix would be

>>> dm = [[dist(i, j) for j in range(n)] for i in range(n)]
+>>> print(dm)
+[[0, 1, 2, 3], [1, 0, 1, 2], [2, 1, 0, 1], [3, 2, 1, 0]]
+

Now from this matrix, we can find the nearest neighbors as follows:

>>> from scipy.stats import rankdata  # type: ignore
+>>> import numpy as np
+>>> rnks = rankdata(dm, axis=1, method="average") - 1
+>>> print(rnks)
+[[0.  1.  2.  3. ]
+ [1.5 0.  1.5 3. ]
+ [3.  1.5 0.  1.5]
+ [3.  2.  1.  0. ]]
+

From the perspective of “1”, “2” is the first nearest neighbor, “3” is the second-nearest neighbor, and “4” is the third-nearest neighbor. From the perspective of “2”, both “1” and “3” are nearest neighbors, so they share rank 1.5. And so on.

Let’s multiply this by 2 to get integers: [If no fractional ranks appear, then we do not need to multiply by 2.]

>>> rnks = np.array(rnks * 2, dtype=int)
+>>> print(rnks)
+[[0 2 4 6]
+ [3 0 3 6]
+ [6 3 0 3]
+ [6 4 2 0]]
+

Now we want to translate this to a flow matrix (NOT a distance matrix). What we want is that there is a big flow from each object to its nearest neighbor, a smaller flow from each object to its second-nearest neighbor, a yet smaller flow to the third nearest neighbor, and so on. So all we need to do is to subtract the elements off the diagonal from 8 = 2*n. [If no fractional elements were there, then we would use n instead of 2*n.] Anyway, we get:

>>> rnks = np.array([[0 if x == 0 else 2*n - x for x in a]
+...                  for a in rnks], int)
+>>> print(rnks)
+[[0 6 4 2]
+ [5 0 5 2]
+ [2 5 0 5]
+ [2 4 6 0]]
+

We can see: The flow from “1” to “2” is 6, which higher than the flow from “1” to “3” (namely 4), and so on. The low from “2” to “1” is 5, which is a bit lower than 6 but higher than 4, because “1” is the “1.5th nearest neighbor of 2”.

Just for good measures, we can weight this qudratically, so we get:

>>> rnks = rnks ** 2
+>>> print(rnks)
+[[ 0 36 16  4]
+ [25  0 25  4]
+ [ 4 25  0 25]
+ [ 4 16 36  0]]
+

This way, we give much more importance to “getting the nearest neighbors right” than getting “far-away neighbors” right. Nice.

OK, but how about the distance matrix for the QAP? What’s the distance? Well, it’s the distance that I would get by arranging the objects in a specific order. It is the difference of the indices in the permutation:

>>> print(np.array([[abs(i - j) for j in range(n)] for i in range(n)], int))
+[[0 1 2 3]
+ [1 0 1 2]
+ [2 1 0 1]
+ [3 2 1 0]]
+

This means: The object that I will place at index 1 has a distance of “1” to the object at index 2. It has a distance of “2” to the object at index 3. It has a distance of “3” to the object at index 4. The object at index 2 will have a distance of “1” to both the objects at indices 1 and 3 and a distance of “2” to the object at index 4. And so on.

In other words, objects that are close in their original space have a big flow between each other. If we put them at directly neighboring indexes, then we will multiply this big flow with a small number. If we put them at distant indices, then we will multiply this big flow with a large number.

Objects that are far away from each other in their original space have a small flow between each other. If we put them at indices that are far apart, we will multiply the larger index-difference = distance with a small number. If we put them close, then we multiply a small distance with a small flow - but we will have to pay for this elsewhere, because then we may need to put objects with a larger flow between each other farther apart.

>>> from moptipyapps.order1d.instance import Instance
+>>> the_instance = Instance.from_sequence_and_distance(
+...     [1, 2, 3, 4], dist, 2, 100, ("bla", ), lambda i: str(i))
+>>> print(the_instance.flows)
+[[ 0 36 16  4]
+ [25  0 25  4]
+ [ 4 25  0 25]
+ [ 4 16 36  0]]
+>>> print(the_instance.distances)
+[[0 1 2 3]
+ [1 0 1 2]
+ [2 1 0 1]
+ [3 2 1 0]]
+

There is one little thing that needs to be done: If we have many objects, then the QAP objective values can get very large. So it makes sense to define a “horizon” after which the relationship for objects are ignored. Above, we chose “100”, which had no impact. If we choose “2”, then only the two nearest neighbors would be considered and we get:

>>> the_instance = Instance.from_sequence_and_distance(
+...     [1, 2, 3, 4], dist, 2, 2, ("bla", ), lambda i: str(i))
+>>> print(the_instance.flows)
+[[ 0 16  4  0]
+ [ 9  0  9  0]
+ [ 0  9  0  9]
+ [ 0  4 16  0]]
+>>> print(the_instance.distances)
+[[0 1 2 3]
+ [1 0 1 2]
+ [2 1 0 1]
+ [3 2 1 0]]
+

The distance matrix of the QAP stays the same, but the flow matrix now has several zeros. Anything farther away than the second-nearest neighbor will be ignored, i.e., get a flow of 0.

Submodules

moptipyapps.order1d.distances module

Some examples for distance metrics.

moptipyapps.order1d.distances.swap_distance(p1, p2)[source]

Compute the swap distance between two permutations p1 and p1.

This is the minimum number of swaps required to translate p1 to p2 and vice versa. This function is symmatric.

An upper bound for the number of maximum number of swaps that could be required is the length of the permutation. This upper bound can be derived from Selection Sort. Imagine that I want to translate the array p1 to p2. I go through p1 from beginning to end. If, at index i, I find the right element (p1[i] == p2[i]), then I do nothing. If not, then the right element must come at some index j>i (because all elements before I already have fixed). So I swap p1[i] with p1[j]. Now p1[i] == p2[i] and I increment i. Once I arrive at the end of p1, it must hold that all(p1[i] == p2[i]). At the same time, I have performed at most one swap at each index during the iteration. Hence, I can never need more swaps than the arrays are long.

Parameters:
  • p1 (ndarray) – the first permutation

  • p2 (ndarray) – the second permutation

Return type:

int

Returns:

the swap distance, always between 0 and len(p1)

>>> swap_distance(np.array([0, 1, 2, 3]), np.array([3, 1, 2, 0]))
+1
+>>> swap_distance(np.array([0, 1, 2]), np.array([0, 1, 2]))
+0
+>>> swap_distance(np.array([1, 0, 2]), np.array([0, 1, 2]))
+1
+>>> swap_distance(np.array([0, 1, 2]), np.array([1, 0, 2]))
+1
+>>> swap_distance(np.array([0, 1, 2]), np.array([2, 0, 1]))
+2
+>>> swap_distance(np.array([2, 0, 1]), np.array([0, 1, 2]))
+2
+>>> swap_distance(np.arange(10), np.array([4, 8, 1, 5, 9, 3, 6, 0, 7, 2]))
+7
+>>> swap_distance(np.array([4, 8, 1, 5, 9, 3, 6, 0, 7, 2]), np.arange(10))
+7
+

moptipyapps.order1d.instance module

An instance of the ordering problem.

class moptipyapps.order1d.instance.Instance(distances, flow_power, horizon, tag_titles, tags, name=None)[source]

Bases: Instance

An instance of the One-Dimensional Ordering Problem.

Such an instance represents the ranking of objects by their distance to each other as a qap problem.

>>> def _dist(a, b):
+...     return abs(a - b)
+>>> def _tags(a):
+...     return f"t{a}"
+>>> the_instance = Instance.from_sequence_and_distance(
+...     [1, 2, 3], _dist, 1, 100, ("bla", ), _tags)
+>>> print(the_instance)
+order1d_3_1
+>>> print(the_instance.distances)
+[[0 1 2]
+ [1 0 1]
+ [2 1 0]]
+>>> print(the_instance.flows)
+[[0 4 2]
+ [3 0 3]
+ [2 4 0]]
+
flow_power: Final[int | float]

the flow power

static from_sequence_and_distance(data, get_distance, flow_power, horizon, tag_titles, get_tags, name=None)[source]

Turn a sequence of objects into a One-Dimensional Ordering instance.

Parameters:
Return type:

Instance

Returns:

the ordering instance

>>> def _dist(a, b):
+...     return abs(a - b)
+>>> def _tags(a):
+...     return f"x{a}", f"b{a}"
+>>> res = Instance.from_sequence_and_distance(
+...     [1, 2, 5], _dist, 2, 100, ("a", "b"), _tags)
+>>> print(res)
+order1d_3_2
+>>> print(res.flows)
+[[0 4 1]
+ [4 0 1]
+ [1 4 0]]
+>>> print(res.distances)
+[[0 1 2]
+ [1 0 1]
+ [2 1 0]]
+>>> res = Instance.from_sequence_and_distance(
+...     [1, 2, 35, 4], _dist, 2, 100, ("a", "b"), _tags)
+>>> print(res)
+order1d_4_2
+>>> print(res.flows)
+[[0 9 1 4]
+ [9 0 1 4]
+ [1 4 0 9]
+ [4 9 1 0]]
+>>> print(res.distances)
+[[0 1 2 3]
+ [1 0 1 2]
+ [2 1 0 1]
+ [3 2 1 0]]
+>>> res = Instance.from_sequence_and_distance(
+...     [1, 2, 4, 4], _dist, 2, 100, ("a", "b"), _tags)
+>>> print(res)
+order1d_3_2
+>>> print(res.flows)
+[[0 4 1]
+ [4 0 1]
+ [1 4 0]]
+>>> print(res.distances)
+[[0 1 2]
+ [1 0 1]
+ [2 1 0]]
+>>> print(res.tags)
+((('x1', 'b1'), 0), (('x2', 'b2'), 1), (('x4', 'b4'), 2), (('x4', 'b4'), 2))
+>>> def _dist2(a, b):
+...     return abs(abs(a) - abs(b)) + 1
+>>> res = Instance.from_sequence_and_distance(
+...     [1, 2, -4, 4], _dist2, 2, 100, ("a", "b"), _tags)
+>>> print(res)
+order1d_4_2
+>>> print(res.flows)
+[[ 0 36  9  9]
+ [36  0  9  9]
+ [ 4 16  0 36]
+ [ 4 16 36  0]]
+>>> print(res.distances)
+[[0 1 2 3]
+ [1 0 1 2]
+ [2 1 0 1]
+ [3 2 1 0]]
+>>> res = Instance.from_sequence_and_distance(
+...     [1, 2, -4, 4], _dist2, 3, 100, ("a", "b"), _tags)
+>>> print(res)
+order1d_4_3
+>>> print(res.flows)
+[[  0 216  27  27]
+ [216   0  27  27]
+ [  8  64   0 216]
+ [  8  64 216   0]]
+>>> print(res.distances)
+[[0 1 2 3]
+ [1 0 1 2]
+ [2 1 0 1]
+ [3 2 1 0]]
+>>> print(res.tags)
+((('x1', 'b1'), 0), (('x2', 'b2'), 1), (('x-4', 'b-4'), 2), (('x4', 'b4'), 3))
+>>> res = Instance.from_sequence_and_distance(
+...     [1, 2, -4, 4], _dist2, 3, 2, ("a", "b"), _tags)
+>>> print(res)
+order1d_4_3_2
+>>> print(res.flows)
+[[0 8 0 0]
+ [8 0 0 0]
+ [0 1 0 8]
+ [0 1 8 0]]
+>>> print(res.distances)
+[[0 1 2 3]
+ [1 0 1 2]
+ [2 1 0 1]
+ [3 2 1 0]]
+>>> print(res.tags)
+((('x1', 'b1'), 0), (('x2', 'b2'), 1), (('x-4', 'b-4'), 2), (('x4', 'b4'), 3))
+>>> res = Instance.from_sequence_and_distance(
+...     [1, 2, -4, 4], _dist2, 2, 2, ("a", "b"), _tags)
+>>> print(res)
+order1d_4_2_2
+>>> print(res.flows)
+[[0 4 0 0]
+ [4 0 0 0]
+ [0 1 0 4]
+ [0 1 4 0]]
+>>> print(res.distances)
+[[0 1 2 3]
+ [1 0 1 2]
+ [2 1 0 1]
+ [3 2 1 0]]
+
horizon: Final[int]

the horizon

log_parameters_to(logger)[source]

Log all parameters of this component as key-value pairs.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

tag_titles: Final[tuple[str, ...]]

the tag titles

tags: Final[tuple[tuple[tuple[str, ...], int], ...]]

the tags, i.e., additional data for each original element

class moptipyapps.order1d.instance.T

the type variable for the source object

alias of TypeVar(‘T’)

moptipyapps.order1d.space module

An extension of the permutation space for one-dimensional ordering.

The main difference is how the to_str result is noted, namely that it contains the mapping of tags to locations.

>>> def _dist(a, b):
+...     return abs(a - b)
+>>> def _tags(a):
+...     return f"t{a}"
+>>> the_instance = Instance.from_sequence_and_distance(
+...     [1, 2, 3, 3, 2, 3], _dist, 2, 10, ("x", ), _tags)
+>>> the_space = OrderingSpace(the_instance)
+>>> the_str = the_space.to_str(np.array([0, 2, 1]))
+>>> the_str.splitlines()
+['0;2;1', '', 'indexZeroBased;suggestedXin01;x', '0;0;t1', '2;1;t2', '2;1;t2', '1;0.5;t3', '1;0.5;t3', '1;0.5;t3']
+>>> print(the_space.from_str(the_str))
+[0 2 1]
+
class moptipyapps.order1d.space.OrderingSpace(instance)[source]

Bases: Permutations

A space for one-dimensional orderings.

from_str(text)[source]

Get the string version from the given text.

Parameters:

text (str) – the text

Return type:

ndarray

Returns:

the string

instance: Final[Instance]

the instance

to_str(x)[source]

Convert a solution to a string.

Parameters:

x (ndarray) – the permutation

Return type:

str

Returns:

the string

diff --git a/moptipyapps.qap.html b/moptipyapps.qap.html new file mode 100644 index 00000000..f0aca682 --- /dev/null +++ b/moptipyapps.qap.html @@ -0,0 +1,55 @@ +moptipyapps.qap package — moptipyapps 0.8.62 documentation

moptipyapps.qap package

The Quadratic Assignment Problem (QAP).

The quadratic assignment problem represents assignments of facilities to locations. Between each pair of facilities, there is a flow of goods. Between each two locations, there is a distance. The goal is to assign facilities to locations such that the overall sum of the products of distance and flow gets minimized. Each instance therefore presents a matrix with distances and a matrix with flows flows. The objective is then to minimize said product sum.

  1. Eliane Maria Loiola, Nair Maria Maia de Abreu, Paulo Oswaldo Boaventura-Netto, Peter Hahn, and Tania Querido. A survey for the Quadratic Assignment Problem. European Journal of Operational Research. 176(2):657-690. January 2007. https://doi.org/10.1016/j.ejor.2005.09.032.

  2. Rainer E. Burkard, Eranda Çela, Panos M. Pardalos, and Leonidas S. Pitsoulis. The Quadratic Assignment Problem. In Ding-Zhu Du, Panos M. Pardalos, eds., Handbook of Combinatorial Optimization, pages 1713-1809, 1998, Springer New York, NY, USA. https://doi.org/10.1007/978-1-4613-0303-9_27.

This is code is part of the research work of Mr. Jiayang Chen (陈嘉阳), a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

Subpackages

Submodules

moptipyapps.qap.instance module

An instance of the Quadratic Assignment Problem.

In this module, we provide the class Instance that encapsulates all information of a problem instance of the Quadratic Assignment Problem (QAP). The QAP aims to locate facilities at locations such that the flow-distance product sum combining a flows of goods between instances with the distances of the locations becomes minimal. Each instance therefore presents a matrix with distances and a matrix with flows flows.

  1. Eliane Maria Loiola, Nair Maria Maia de Abreu, Paulo Oswaldo Boaventura-Netto, Peter Hahn, and Tania Querido. A survey for the Quadratic Assignment Problem. European Journal of Operational Research. 176(2):657-690. January 2007. https://doi.org/10.1016/j.ejor.2005.09.032.

  2. Rainer E. Burkard, Eranda Çela, Panos M. Pardalos, and Leonidas S. Pitsoulis. The Quadratic Assignment Problem. In Ding-Zhu Du, Panos M. Pardalos, eds., Handbook of Combinatorial Optimization, pages 1713-1809, 1998, Springer New York, NY, USA. https://doi.org/10.1007/978-1-4613-0303-9_27.

We additionally provide access to several standard QAP benchmark instances via the from_resource() and list_resources() methods. The standard benchmark instances stem from QAPLIB, a library of QAP instances, which can be found at <https://qaplib.mgi.polymtl.ca> and <https://coral.ise.lehigh.edu/data-sets/qaplib>.

  1. QAPLIB - A Quadratic Assignment Problem Library. The Websites <https://qaplib.mgi.polymtl.ca/> (updated 2018) and <https://coral.ise.lehigh.edu/data-sets/qaplib/> (updated 2011), including the benchmark instances, on visited 2023-10-21.

  2. Rainer E. Burkard, Stefan E. Karisch, and Franz Rendl. QAPLIB - A Quadratic Assignment Problem Library. Journal of Global Optimization. 10:391-403, 1997. https://doi.org/10.1023/A:1008293323270.

class moptipyapps.qap.instance.Instance(distances, flows, lower_bound=None, upper_bound=None, name=None)[source]

Bases: Component

An instance of the Quadratic Assignment Problem.

bks()[source]

Get the best-known solution, if known, the optimum, or lower bound.

A tuple with a string identifying the source of the value and a value corresponding to the best-known solution:

  • (“OPT”, xxx): the problem instance has been solved to optimality and xxx is the objective value of the optimum

  • (“LB”, xxx): neither the optimum nor a best-known solution are available for this instance, so we return the lower bound

The data is based on https://qaplib.mgi.polymtl.ca/, visited on 2024-05-09. The following sources are included:

  • “FF1993GHFTQAP”: Charles Fleurent and Jacques A. Ferland. Genetic Hybrids for the Quadratic Assignment Problem. In Panos M. Pardalos and Henry Wolkowicz, eds, Quadratic Assignment and Related Problems, Proceedings of a DIMACS Workshop, May 20-21, 1993. pages 173-187. Providence, RI, USA: American Mathematical Society.

  • “M2003AMSAAFTQAP”: Alfonsas Misevičius, A Modified Simulated Annealing Algorithm for the Quadratic Assignment Problem. Informatica 14(4):497-514. January 2003. https://doi.org/10.15388/Informatica.2003.037.

  • “M2005ATSAFTQAP”: Alfonsas Misevičius. A Tabu Search Algorithm for the Quadratic Assignment Problem. Computational Optimization and Applications 30(1):95-111. 2005. https://doi.org/10.1007/s10589-005-4562-x.

  • “M2008AIOTITSAFTQAP”: Alfonsas Misevičius. An Implementation of the Iterated Tabu Search Algorithm for the Quadratic Assignment Problem. Working Paper. 2008. Kaunas, Lithuania: Kaunas University of Technology.

  • “S1997MMASFQAP”: Thomas Stützle. MAX-MIN Ant System for Quadratic Assignment Problems. Research Report AIDA-97-04. 1997. Darmstadt, Germany: Department of Computer Schience, Darmstadt University of Technology.

  • “T1991RTSFTQAP”: Éric Taillard. Robust Taboo Search for the Quadratic Assignment Problem. Parallel Computing. 17(4-5):443-455. July 1991.

  • “T1995COISFTQAP”: Éric D. Taillard. Comparison of Iterative Searches for the Quadratic Assignment Problem. Location Science 3(2):87-105. 1995.

  • “TB1994AISAAFTQAP”: Ulrich Wilhelm Thonemann and Andreas Bölte. An Improved Simulated Annealing Algorithm for the Quadratic Assignment Problem. Working Paper 1994. Paderborn, Germany: School of Business, Department of Production and Operations Research, University of Paderborn.

  • “TG1997AMFTQAP”: Éric D. Taillard and Luca-Maria Gambardella. Adaptive Memories for the Quadratic Assignment Problem. 1997. Technical Report IDSIA-87-97. Lugano, Switzerland: IDSIA.

Return type:

tuple[str, int]

Returns:

a tuple[str, int] with the objective value of the best-known (if any is available), the optimum, or the lower bound.

distances: Final[ndarray]

the distance matrix

flows: Final[ndarray]

the flows

static from_qaplib_stream(stream, lower_bound=None, upper_bound=None, name=None)[source]

Load an instance from a QAPLib-formatted stream.

Parameters:
  • stream (Iterable[str]) – the stream to load the data from

  • lower_bound (int | None, default: None) – the optional lower bound

  • upper_bound (int | None, default: None) – the optional upper bound

  • name (str | None, default: None) – the name of this instance

Return type:

Instance

Returns:

the instance

>>> ins = Instance.from_qaplib_stream([
+...         "4", "",
+...         "1 2 3 4 5 6 7 8 9 10 11 12 13",
+...         "   14    15   16  ", "",
+...         "17 18 19 20 21 22 23 24 25 26     27",
+...         " 28   29 30 31   32"])
+>>> ins.distances
+array([[17, 18, 19, 20],
+       [21, 22, 23, 24],
+       [25, 26, 27, 28],
+       [29, 30, 31, 32]], dtype=int16)
+>>> ins.flows
+array([[ 1,  2,  3,  4],
+       [ 5,  6,  7,  8],
+       [ 9, 10, 11, 12],
+       [13, 14, 15, 16]], dtype=int16)
+>>> ins.lower_bound
+2992
+>>> ins.upper_bound
+3672
+>>> ins.name
+'qap4_2992_3672'
+
static from_resource(name)[source]

Load a QAP-Lib instance from a resource.

The original data can be found at <https://qaplib.mgi.polymtl.ca> and <https://coral.ise.lehigh.edu/data-sets/qaplib>.

Parameters:

name (str) – the name string

Return type:

Instance

Returns:

the instance

>>> insta = Instance.from_resource("chr25a")
+>>> print(insta.n)
+25
+>>> print(insta.name)
+chr25a
+>>> print(insta.lower_bound)
+3796
+>>> print(insta.upper_bound)
+50474
+
static list_resources()[source]

Get a tuple of all the QAP-lib instances available as resource.

The original data can be found at <https://qaplib.mgi.polymtl.ca> and <https://coral.ise.lehigh.edu/data-sets/qaplib>.

Return type:

tuple[str, ...]

Returns:

the tuple with the instance names

>>> len(Instance.list_resources())
+134
+
log_parameters_to(logger)[source]

Log all parameters of this instance as key-value pairs.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

lower_bound: Final[int]

the lower bound for the QAP objective

n: Final[int]

the scale of the problem

name: Final[str]

the name of this instance

upper_bound: Final[int]

the upper bound for the QAP objective

moptipyapps.qap.instance.trivial_bounds(distances, flows)[source]

Compute the trivial bounds for the QAP objective.

A trivial upper bound is to multiply the largest flow with the largest distance, the second-largest flow with the second-largest distance, the third-largest flow with the third-largest distance, and so on.

A trivial lower bound is to multiply the largest flow with the shortest distance, the second-largest flow with the second-shortest distance, the third-largest flow with the third-shortest distance, and so on.

Parameters:
  • distances (ndarray) – the distance matrix

  • flows (ndarray) – the flow matrix

Return type:

tuple[int, int]

Returns:

the lower and upper bounds

>>> dst = np.array([[0, 1, 2], [3, 0, 4], [5, 6, 0]])
+>>> flws = np.array([[0, 95, 86], [23, 0, 55], [24, 43, 0]])
+>>> 0*95 + 0*86 + 0*55 + 1*43 + 2*24 + 3*23 + 4*0 + 5*0 + 6*0
+160
+>>> 6*95 + 5*86 + 4*55 + 3*43 + 2*24 + 1*23 + 0*0 + 0*0 + 0*0
+1420
+>>> trivial_bounds(dst, flws)
+(160, 1420)
+

moptipyapps.qap.objective module

The objective function for the Quadratic Assignment Problem.

The candidate solutions to the QAP are permutations p of length n of the numbers 0, 1, …, n-1. An instance of the Quadratic Assignment Problem (QAP) presents a n*n matrix D with distances and a n*n matrix F with flows flows. The objective value, subject to minimization, is then the sum( F[i,j] * D[p[i], p[j]] for i, j in 0..n-1 ), i.e., the sum of the products of the flows between facilities and the distances of their assigned locations.

  1. Eliane Maria Loiola, Nair Maria Maia de Abreu, Paulo Oswaldo Boaventura-Netto, Peter Hahn, and Tania Querido. A survey for the Quadratic Assignment Problem. European Journal of Operational Research. 176(2):657-690. January 2007. https://doi.org/10.1016/j.ejor.2005.09.032.

  2. Rainer E. Burkard, Eranda Çela, Panos M. Pardalos, and Leonidas S. Pitsoulis. The Quadratic Assignment Problem. In Ding-Zhu Du, Panos M. Pardalos, eds., Handbook of Combinatorial Optimization, pages 1713-1809, 1998, Springer New York, NY, USA. https://doi.org/10.1007/978-1-4613-0303-9_27.

>>> inst = Instance.from_resource("bur26a")
+>>> QAPObjective(inst).evaluate(np.array([
+...     25, 14, 10, 6, 3, 11, 12, 1, 5, 17, 0, 4, 8, 20,
+...     7, 13, 2, 19, 18, 24, 16, 9, 15, 23, 22, 21]))
+5426670
+
>>> inst = Instance.from_resource("nug12")
+>>> QAPObjective(inst).evaluate(np.array([
+...     11, 6, 8, 2, 3, 7, 10, 0, 4, 5, 9, 1]))
+578
+
>>> inst = Instance.from_resource("tai12a")
+>>> QAPObjective(inst).evaluate(np.array([
+...     7, 0, 5, 1, 10, 9, 2, 4, 8, 6, 11, 3]))
+224416
+
class moptipyapps.qap.objective.QAPObjective(instance)[source]

Bases: Objective

An objective function for the quadratic assignment problem.

evaluate(x)[source]

Compute the quadratic assignment problem objective value.

Parameters:

x – the permutation of elements

Return type:

float

Returns:

the sum of flows times distances

instance: Final[Instance]

the instance data

log_parameters_to(logger)[source]

Log all parameters of this component as key-value pairs.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

lower_bound()[source]

Get the lower bound of this objective function.

Return type:

float

Returns:

the lower bound

upper_bound()[source]

Get the upper bound of this objective function.

Return type:

float

Returns:

the upper bound

diff --git a/moptipyapps.qap.qaplib.html b/moptipyapps.qap.qaplib.html new file mode 100644 index 00000000..95149c37 --- /dev/null +++ b/moptipyapps.qap.qaplib.html @@ -0,0 +1 @@ +moptipyapps.qap.qaplib package — moptipyapps 0.8.62 documentation

moptipyapps.qap.qaplib package

QAPLIB – A Quadratic Assignment Problem Library.

  1. QAPLIB - A Quadratic Assignment Problem Library. The Websites <https://qaplib.mgi.polymtl.ca/> (updated 2018) and <https://coral.ise.lehigh.edu/data-sets/qaplib/> (updated 2011), including the benchmark instances, on visited 2023-10-21.

  2. Rainer E. Burkard, Stefan E. Karisch, and Franz Rendl. QAPLIB - A Quadratic Assignment Problem Library. Journal of Global Optimization. 10:391-403, 1997. https://doi.org/10.1023/A:1008293323270.

moptipyapps.qap.qaplib.open_resource_stream(file_name)[source]

Open a QAPLib resource stream.

Parameters:

file_name (str) – the file name of the resource

Return type:

TextIO

Returns:

the stream

diff --git a/moptipyapps.tests.html b/moptipyapps.tests.html new file mode 100644 index 00000000..261731ae --- /dev/null +++ b/moptipyapps.tests.html @@ -0,0 +1 @@ +moptipyapps.tests package — moptipyapps 0.8.62 documentation

moptipyapps.tests package

Unit tests that you can use to check your moptipyapps based experiments.

Submodules

moptipyapps.tests.on_binpacking2d module

Perform tests on the Two-Dimensional Bin Packing Problem.

moptipyapps.tests.on_binpacking2d.binpacking_instances_for_tests(random=Generator(PCG64) at 0x7F2D51E91B60)[source]

Get a sequence of 2D Bin Packing instances to test on.

Parameters:

random (Generator, default: Generator(PCG64) at 0x7F2D51E91B60) – the random number generator to use

Return type:

Iterable[str]

Returns:

an iterable of 2D Bin Packing instance names

moptipyapps.tests.on_binpacking2d.make_packing_invalid(random=Generator(PCG64) at 0x7F2D51E91B60)[source]

Make a function that creates invalid packings.

Parameters:

random (Generator, default: Generator(PCG64) at 0x7F2D51E91B60) – the random number generator to use

Return type:

Callable[[Packing], Packing]

Returns:

a function that can make packings invalid

moptipyapps.tests.on_binpacking2d.make_packing_valid(inst, random=Generator(PCG64) at 0x7F2D51E91B60)[source]

Make a function that creates valid packings.

Parameters:
  • inst (Instance) – the two-dimensional bin packing instance

  • random (Generator, default: Generator(PCG64) at 0x7F2D51E91B60) – the random number generator to use

Return type:

Callable[[Generator, Packing], Packing]

Returns:

a function that can make packings valid

moptipyapps.tests.on_binpacking2d.validate_algorithm_on_1_2dbinpacking(algorithm, instance=None, max_fes=100, random=Generator(PCG64) at 0x7F2D51E91B60)[source]

Check the validity of a black-box algorithm on the 2d bin packing.

Parameters:
Return type:

None

moptipyapps.tests.on_binpacking2d.validate_algorithm_on_2dbinpacking(algorithm, max_fes=100, random=Generator(PCG64) at 0x7F2D51E91B60)[source]

Validate an algorithm on a set of bin packing instances.

Parameters:
Return type:

None

moptipyapps.tests.on_binpacking2d.validate_objective_on_1_2dbinpacking(objective, instance=None, random=Generator(PCG64) at 0x7F2D51E91B60)[source]

Validate an objective function on 1 2D bin packing instance.

Parameters:
Return type:

None

moptipyapps.tests.on_binpacking2d.validate_objective_on_2dbinpacking(objective, random=Generator(PCG64) at 0x7F2D51E91B60)[source]

Validate an objective function on bin packing instances.

Parameters:
Return type:

None

moptipyapps.tests.on_binpacking2d.validate_signed_permutation_encoding_on_1_2dbinpacking(encoding, instance=None, random=Generator(PCG64) at 0x7F2D51E91B60)[source]

Validate a signed permutation encoding on one 2D bin packing instance.

Parameters:
Return type:

None

moptipyapps.tests.on_binpacking2d.validate_signed_permutation_encoding_on_2dbinpacking(encoding, random=Generator(PCG64) at 0x7F2D51E91B60)[source]

Validate a signed permutation encoding function on bin packing instances.

Parameters:
Return type:

None

moptipyapps.tests.on_tsp module

Perform tests on the Traveling Salesperson Problem.

moptipyapps.tests.on_tsp.make_tour_invalid(random, y)[source]

Create invalid tours.

Parameters:
  • random (Generator) – the random number generator to use

  • y (ndarray) – the input tour

Return type:

ndarray

Returns:

the invalid version of y

moptipyapps.tests.on_tsp.make_tour_valid(random, y)[source]

Create valid TSP tours.

Parameters:
  • random (Generator) – the random number generator to use

  • y (ndarray) – the input tour

Return type:

ndarray

Returns:

the valid version of y

moptipyapps.tests.on_tsp.tsp_instances_for_tests(random=Generator(PCG64) at 0x7F2D51E93A00, symmetric=True, asymmetric=True)[source]

Get a sequence of TSP instances to test on.

Parameters:
  • random (Generator, default: Generator(PCG64) at 0x7F2D51E93A00) – the random number generator to use

  • symmetric (bool, default: True) – include symmetric instances

  • asymmetric (bool, default: True) – include asymmetric instances

Return type:

Iterable[str]

Returns:

an iterable of TSP instance names

moptipyapps.tests.on_tsp.validate_algorithm_on_1_tsp(algorithm, instance=None, max_fes=256, random=Generator(PCG64) at 0x7F2D51E93A00)[source]

Check the validity of a black-box algorithm on one TSP instance.

Parameters:
  • algorithm (Union[Algorithm, Callable[[Instance, Permutations], Algorithm]]) – the algorithm or algorithm factory

  • instance (str | None, default: None) – the instance name, or None to randomly pick one

  • max_fes (int, default: 256) – the maximum number of FEs

  • random (Generator, default: Generator(PCG64) at 0x7F2D51E93A00) – the default random generator to use

Return type:

None

moptipyapps.tests.on_tsp.validate_algorithm_on_tsp(algorithm, symmetric=True, asymmetric=True, max_fes=256, random=Generator(PCG64) at 0x7F2D51E93A00)[source]

Validate an algorithm on a set of TSP instances.

Parameters:
  • algorithm (Callable[[Instance, Permutations], Algorithm]) – the algorithm factory

  • symmetric (bool, default: True) – include symmetric instances

  • asymmetric (bool, default: True) – include asymmetric instances

  • max_fes (int, default: 256) – the maximum FEs

  • random (Generator, default: Generator(PCG64) at 0x7F2D51E93A00) – the random number generator

Return type:

None

moptipyapps.tests.on_tsp.validate_objective_on_1_tsp(objective, instance=None, random=Generator(PCG64) at 0x7F2D51E93A00)[source]

Validate an objective function on 1 TSP instance.

Parameters:
Return type:

None

moptipyapps.tests.on_tsp.validate_objective_on_tsp(objective, symmetric=True, asymmetric=True, random=Generator(PCG64) at 0x7F2D51E93A00)[source]

Validate an objective function on TSP instances.

Parameters:
  • objective (Union[Objective, Callable[[Instance], Objective]]) – the objective function or a factory creating it

  • symmetric (bool, default: True) – include symmetric instances

  • asymmetric (bool, default: True) – include asymmetric instances

  • random (Generator, default: Generator(PCG64) at 0x7F2D51E93A00) – the random number generator

Return type:

None

diff --git a/moptipyapps.tsp.html b/moptipyapps.tsp.html new file mode 100644 index 00000000..7be8add2 --- /dev/null +++ b/moptipyapps.tsp.html @@ -0,0 +1,117 @@ +moptipyapps.tsp package — moptipyapps 0.8.62 documentation

moptipyapps.tsp package

Experiments with the Traveling Salesperson Problem (TSP).

A Traveling Salesperson Problem (TSP) is defined as a fully-connected graph with n_cities nodes. Each edge in the graph has a weight, which identifies the distance between the nodes. The goal is to find the shortest tour that visits every single node in the graph exactly once and then returns back to its starting node. Then nodes are usually called cities. In this file, we present methods for loading instances of the TSP as distance matrices A. In other words, the value at A[i, j] identifies the travel distance from i to j. Such instance data can be loaded via class instance.

In this package, we provide the following tools:

  • instance allows you to load instance data in the TSPLib format and it provides several instances from TSPLib as resources.

  • known_optima provides known optimal solutions for some of the TSPLib instances. These should mainly be used for testing purposes.

  • tour_length is an objective function that can efficiently computed the length of a tour in path representation.

  • tsplib is just a dummy package holding the actual TSPLib data resources.

Important initial work on this code has been contributed by Mr. Tianyu LIANG (梁天宇), <liangty@stu.hfuu.edu.cn> a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

Subpackages

Submodules

moptipyapps.tsp.ea1p1_revn module

A (1+1) EA for the TSP using the reversal move.

A (1+1) EA is basically a local search rls algorithm that starts with a random solution as the current solution x. In each iteration, it samples one new solution from the neighborhood of x and accepts it (as the new x), if it is better or equally good. The algorithm here applies the reversal move, i.e., a two-opt move, as the unary search operator used for sampling the neighborhood of x. The interesting thing about this operator, when applied to the TSP, is that we can compute the tour length of the new solution in O(1) from the tour length of the current solution. This means we can very quickly decide whether the move would be acceptable - and only apply it (in O(n)) if its.

The algorithm implemented here is the same as the basic (1+1) EA with rev operator in the paper [1].

The original version of this code has been contributed by Mr. Tianyu LIANG (梁天宇), <liangty@stu.hfuu.edu.cn> a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

  1. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, and Thomas Weise. Solving the Traveling Salesperson Problem using Frequency Fitness Assignment. In Hisao Ishibuchi, Chee-Keong Kwoh, Ah-Hwee Tan, Dipti Srinivasan, Chunyan Miao, Anupam Trivedi, and Keeley A. Crockett, editors, Proceedings of the IEEE Symposium on Foundations of Computational Intelligence (IEEE FOCI’22), part of the IEEE Symposium Series on Computational Intelligence (SSCI 2022). December 4-7, 2022, Singapore, pages 360-367. IEEE. https://doi.org/10.1109/SSCI51031.2022.10022296.

  2. Pedro Larrañaga, Cindy M. H. Kuijpers, Roberto H. Murga, Iñaki Inza, and S. Dizdarevic. Genetic Algorithms for the Travelling Salesman Problem: A Review of Representations and Operators. Artificial Intelligence Review, 13(2):129-170, April 1999. Kluwer Academic Publishers, The Netherlands. https://doi.org/10.1023/A:1006529012972.

class moptipyapps.tsp.ea1p1_revn.TSPEA1p1revn(instance)[source]

Bases: Algorithm

A (1+1) EA using the reversal operator for the TSP.

log_parameters_to(logger)[source]

Log the parameters of the algorithm to the given logger.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

solve(process)[source]

Apply an RLS = (1+1) EA optimization process with reversing operator.

Parameters:

process (Process) – the process instance which provides random numbers, functions for creating, copying, and evaluating solutions, as well as the termination criterion

Return type:

None

moptipyapps.tsp.ea1p1_revn.rev_if_not_worse(i, j, n_cities, dist, x, y)[source]

Check a reversal move, apply it if it is better, return new tour length.

Parameters:
  • i (int) – the first, smaller index

  • j (int) – the second, larger index

  • n_cities (int) – the number of cities

  • dist (ndarray) – the problem instance

  • x (ndarray) – the candidate solution

  • y (int) – the tour length

Return type:

int

moptipyapps.tsp.fea1p1_revn module

A (1+1) FEA for the TSP using the reversal move.

A (1+1) FEA is the same as the (1+1) EA with Frequency Fitness Assignment. The (1+1) EA using the same search operator as this algorithm here is implemented in module ea1p1_revn. The algorithm implemented here is the same as the basic (1+1) FEA with rev operator in the paper [1].

The original version of this code has been contributed by Mr. Tianyu LIANG (梁天宇), <liangty@stu.hfuu.edu.cn> a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

  1. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, and Thomas Weise. Solving the Traveling Salesperson Problem using Frequency Fitness Assignment. In Hisao Ishibuchi, Chee-Keong Kwoh, Ah-Hwee Tan, Dipti Srinivasan, Chunyan Miao, Anupam Trivedi, and Keeley A. Crockett, editors, Proceedings of the IEEE Symposium on Foundations of Computational Intelligence (IEEE FOCI’22), part of the IEEE Symposium Series on Computational Intelligence (SSCI 2022). December 4-7, 2022, Singapore, pages 360-367. IEEE. https://doi.org/10.1109/SSCI51031.2022.10022296.

  2. Pedro Larrañaga, Cindy M. H. Kuijpers, Roberto H. Murga, Iñaki Inza, and S. Dizdarevic. Genetic Algorithms for the Travelling Salesman Problem: A Review of Representations and Operators. Artificial Intelligence Review, 13(2):129-170, April 1999. Kluwer Academic Publishers, The Netherlands. https://doi.org/10.1023/A:1006529012972.

class moptipyapps.tsp.fea1p1_revn.TSPFEA1p1revn(instance, do_log_h=False)[source]

Bases: Algorithm

A (1+1) FEA using the reversal operator for the TSP.

do_log_h: Final[bool]

shall we log the h-table`?

instance: Final[Instance]

the instance

log_parameters_to(logger)[source]

Log the parameters of the algorithm to the given logger.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

solve(process)[source]

Apply a (1+1) FEA optimization process with reversing operator.

Parameters:

process (Process) – the process instance which provides random numbers, functions for creating, copying, and evaluating solutions, as well as the termination criterion

Return type:

None

moptipyapps.tsp.fea1p1_revn.rev_if_h_not_worse(i, j, n_cities, dist, h, x, y)[source]

Apply a reversal move if its tour length frequency is not worse.

Parameters:
  • i (int) – the first, smaller index

  • j (int) – the second, larger index

  • n_cities (int) – the number of cities

  • dist (ndarray) – the problem instance

  • h (ndarray) – the frequency table

  • x (ndarray) – the candidate solution

  • y (int) – the tour length

Return type:

int

moptipyapps.tsp.instance module

An instance of the Traveling Salesperson Problem (TSP) as distance matrix.

An instance of the Traveling Salesperson Problem (TSP) is defined as a fully-connected graph with Instance.n_cities nodes. Each edge in the graph has a weight, which identifies the distance between the nodes. The goal is to find the shortest tour that visits every single node in the graph exactly once and then returns back to its starting node. Then nodes are usually called cities. In this file, we present methods for loading instances of the TSP as distance matrices A. In other words, the value at A[i, j] identifies the travel distance from i to j.

We can load files in a subset of the TSPLib format [1, 2] and also include the instances of TSPLib with no more than 2000 cities. We load instances as full distance matrices, which makes writing algorithms to solve them easier but handling instances with more than 2000 cities problematic due to the high memory consumption. Of course, you could still load them, but it would be ill-advised to do so on a normal PC (at the time of this writing).

The TSPLib contains many instances of the symmetric TSP, where the distance A[i, j] from city i to city j is the same as the distance A[j, i] from j to i. Here, we provide 111 of them as resource. The cities of some of these instances are points in the Euclidean plain and the distances are the (approximate) Euclidean distances. Others use geographical coordinates (longitude/latitude), and yet others are provides as distances matrices directly. We also provide 19 of the asymmetric TSPLib instances, where the distance A[i, j] from i to j may be different from the distance A[j, i] from j to i.

You can obtain lists of either all, only the symmetric, or only the asymmetric instance resources via

>>> print(Instance.list_resources()[0:10])  # get all instances (1..10)
+('a280', 'ali535', 'att48', 'att532', 'bayg29', 'bays29', 'berlin52', 'bier127', 'br17', 'brazil58')
+
>>> print(Instance.list_resources(asymmetric=False)[0:10])  # only symmetric
+('a280', 'ali535', 'att48', 'att532', 'bayg29', 'bays29', 'berlin52', 'bier127', 'brazil58', 'brg180')
+
>>> print(Instance.list_resources(symmetric=False)[0:10])  # only asymmetric
+('br17', 'ft53', 'ft70', 'ftv170', 'ftv33', 'ftv35', 'ftv38', 'ftv44', 'ftv47', 'ftv55')
+

You can load any of these instances from the resources via Instance.from_resource() as follows:

>>> inst = Instance.from_resource("a280")
+>>> print(inst.n_cities)
+280
+

If you want to read an instance from an external TSPLib file, you can use Instance.from_file(). Be aware that not the whole TSPLib standard is supported right now, but only a reasonably large subset.

Every TSP instance automatically provides a lower bound Instance.tour_length_lower_bound and an upper bound Instance.tour_length_upper_bound of the lengths of valid tours. For the TSPLib instances, the globally optimal solutions and their tour lengths are known, so we can use them as lower bounds directly. Otherwise, we currently use a very crude approximation: We assume that, for each city i, the next city j to be visited would be the nearest neighbor of i. Of course, in reality, such a tour may not be feasible, as it could contain disjoint sets of loops. But no tour can be shorter than this. As upper bound, we do the same but assume that j would be the cities farthest away from i.

>>> print(inst.tour_length_lower_bound)
+2579
+>>> print(inst.tour_length_upper_bound)
+65406
+

It should be noted that all TSPLib instances by definition have integer distances. This means that there are never floating point distances and, for example, Euclidean distances are rounded and are only approximate Euclidean. Then again, since floating point numbers could also only represent things such as sqrt(2) approximately, using integers instead of floating point numbers does not really change anything - distances would be approximately Euclidean or approximately geographical either way.

TSPLib also provides some known optimal solutions in path representation, i.e., as permutations of the numbers 0 to n_cities-1. The optimal solutions corresponding to the instances provides as resources can be obtained via known_optima.

>>> inst = Instance.from_resource("si175")
+>>> print(inst.n_cities)
+175
+>>> inst[0, 1]
+113
+>>> inst[173, 174]
+337
+>>> inst[1, 174]
+384
+>>> inst[2, 174]
+384
+>>> inst[2, 3]
+248
+>>> inst[3, 5]
+335
+>>> inst[4, 6]
+134
+

The original data of TSPLib can be found at <http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/>. Before doing anything with these data directly, you should make sure to read the FAQ <http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/TSPFAQ.html> and the documentation <http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp95.pdf>.

On 2024-10-18, we added a small eleven city instance based on the car travel distances in km between the Chinese cities Shanghai (上海), Beijing (北京), Nanjing (南京), Hefei (合肥), Harbin (哈尔滨), Kunming (昆明), Wuhan (武汉), Xi’an (西安), Chongqing (重庆), Changsha (长沙), and Hong Kong (香港), determined using BaiDu Maps on 2024-10-18. The optimal solution for this instance has length 9547 (km) and visits the cities in the following order: Shanghai (上海), Nanjing (南京), Hefei (合肥), Wuhan (武汉), Changsha (长沙), Hong Kong (香港), Kunming (昆明), Chongqing (重庆), Xi’an (西安), Beijing (北京), Harbin (哈尔滨). This instance can be used to validate and santity-test algorithms, as its solution can easily be determined using exhaustive enumeration.

Interestingly, it holds that the calculated travel distance from Harbin (哈尔滨) to Kunming (昆明) is longer than the travel distance from Harbin (哈尔滨) to Xi’an (西安) and then from Xi’an (西安) to Kunming (昆明).

  • Harbin (哈尔滨) - Kunming (昆明) = 3781 km

  • Harbin (哈尔滨) - Xi’an (西安) = 2291 km

  • Xi’an (西安) - Kunming (昆明) = 1483 km

  • 2291 km + 1483 km = 3774 km, which is less than 3781 km

This may be because BaiduMaps ranked the paths by travel time and not travel distance, or by any other strange effect I cannot account for. Maybe it is related to which lane of a road one would naturally drive on or lengths of intersections depending on the direction one is coming from. Either way, this shows that this simple instance cn11 may already have interesting properties. It violates the triangle equation and it is non-Euclidean. It is also not based on Geo-coordinates, but on actual street distances and times.

>>> inst = Instance.from_resource("cn11")
+>>> inst[0, 0]
+0
+>>> inst[1, 2]
+1007
+>>> inst[1, 3]
+1017
+>>> inst[9, 10]
+830
+>>> inst[5, 6]
+1560
+

Important initial work on this code has been contributed by Mr. Tianyu LIANG (梁天宇), <liangty@stu.hfuu.edu.cn> a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

  1. Gerhard Reinelt. TSPLIB - A Traveling Salesman Problem Library. ORSA Journal on Computing 3(4):376-384. November 1991. https://doi.org/10.1287/ijoc.3.4.376. http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/

  2. Gerhard Reinelt. TSPLIB95. Heidelberg, Germany: Universität Heidelberg, Institut für Angewandte Mathematik. 1995. http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp95.pdf

  3. David Lee Applegate, Robert E. Bixby, Vašek Chvátal, and William John Cook. The Traveling Salesman Problem: A Computational Study. Second Edition, 2007. Princeton, NJ, USA: Princeton University Press. Volume 17 of Princeton Series in Applied Mathematics. ISBN: 0-691-12993-2.

  4. Gregory Z. Gutin and Abraham P. Punnen. The Traveling Salesman Problem and its Variations. 2002. Kluwer Academic Publishers. Volume 12 of Combinatorial Optimization. https://doi.org/10.1007/b101971.

  5. Pedro Larrañaga, Cindy M. H. Kuijpers, Roberto H. Murga, Iñaki Inza, and Sejla Dizdarevic. Genetic Algorithms for the Travelling Salesman Problem: A Review of Representations and Operators. Artificial Intelligence Review 13(2):129-170. April 1999. https://doi.org/10.1023/A:1006529012972.

  6. Eugene Leighton Lawler, Jan Karel Lenstra, Alexander Hendrik George Rinnooy Kan, and David B. Shmoys. The Traveling Salesman Problem: A Guided Tour of Combinatorial Optimization. September 1985. Chichester, West Sussex, UK: Wiley Interscience. In Estimation, Simulation, and Control - Wiley-Interscience Series in Discrete Mathematics and Optimization. ISBN: 0-471-90413-9

  7. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, and Thomas Weise. Solving the Traveling Salesperson Problem using Frequency Fitness Assignment. In Proceedings of the IEEE Symposium on Foundations of Computational Intelligence (IEEE FOCI’22), part of the IEEE Symposium Series on Computational Intelligence (SSCI’22), December 4-7, 2022, Singapore. Pages 360-367. IEEE. https://doi.org/10.1109/SSCI51031.2022.10022296.

  8. Thomas Weise, Raymond Chiong, Ke Tang, Jörg Lässig, Shigeyoshi Tsutsui, Wenxiang Chen, Zbigniew Michalewicz, and Xin Yao. Benchmarking Optimization Algorithms: An Open Source Framework for the Traveling Salesman Problem. IEEE Computational Intelligence Magazine. 9(3):40-52. August 2014. https://doi.org/10.1109/MCI.2014.2326101.

class moptipyapps.tsp.instance.Instance(name: str, tour_length_lower_bound: int, matrix: ndarray, upper_bound_range_multiplier: int = 1)[source]

Bases: Component, ndarray

An instance of the Traveling Salesperson Problem.

static from_file(path, lower_bound_getter=<built-in method __getitem__ of dict object>)[source]

Read a TSP Lib instance from a TSP-lib formatted file.

Parameters:
  • path (str) – the path to the file

  • lower_bound_getter (Optional[Callable[[str], int]], default: <built-in method __getitem__ of dict object at 0x7f2d51cf11c0>) – a function returning a lower bound for an instance name, or None to use a simple lower bound approximation

Return type:

Instance

Returns:

the instance

>>> from os.path import dirname
+>>> inst = Instance.from_file(dirname(__file__) + "/tsplib/br17.atsp")
+>>> inst.name
+'br17'
+
static from_resource(name)[source]

Load a TSP instance from a resource.

Parameters:

name (str) – the name string

Return type:

Instance

Returns:

the instance

>>> Instance.from_resource("br17").n_cities
+17
+
is_symmetric: bool

is the TSP instance symmetric?

static list_resources(symmetric=True, asymmetric=True)[source]

Get a tuple of all the TSP instances available as resource.

Parameters:
  • symmetric (bool, default: True) – include the symmetric instances

  • asymmetric (bool, default: True) – include the asymmetric instances

Return type:

tuple[str, ...]

Returns:

the tuple with the instance names

>>> a = len(Instance.list_resources(True, True))
+>>> print(a)
+111
+>>> b = len(Instance.list_resources(True, False))
+>>> print(b)
+92
+>>> c = len(Instance.list_resources(False, True))
+>>> print(c)
+19
+>>> print(a == (b + c))
+True
+>>> print(len(Instance.list_resources(False, False)))
+0
+
log_parameters_to(logger)[source]

Log the parameters of the instance to the given logger.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

>>> from moptipy.utils.logger import InMemoryLogger
+>>> with InMemoryLogger() as l:
+...     with l.key_values("I") as kv:
+...         Instance.from_resource("kroE100").log_parameters_to(kv)
+...     print(repr('@'.join(l.get_log())))
+'BEGIN_I@name: kroE100@class: moptipyapps.tsp.instance.Instance@nCities: 100@tourLengthLowerBound: 22068@tourLengthUpperBound: 330799@symmetric: T@dtype: i@END_I'
+
n_cities: int

the number of cities

name: str

the name of the instance

to_stream(collector, comments=())[source]

Convert this instance to a stream.

Parameters:
Return type:

None

>>> orig = Instance.from_resource("br17")
+>>> text = []
+>>> orig.to_stream(text.append)
+>>> reload = _from_stream(iter(text),
+...     lambda _: orig.tour_length_lower_bound)
+>>> orig.n_cities == reload.n_cities
+True
+>>> orig.name == reload.name
+True
+>>> list(orig.flatten()) == list(reload.flatten())
+True
+
>>> orig = Instance.from_resource("att48")
+>>> text = []
+>>> orig.to_stream(text.append)
+>>> reload = _from_stream(iter(text),
+...     lambda _: orig.tour_length_lower_bound)
+>>> orig.n_cities == reload.n_cities
+True
+>>> orig.name == reload.name
+True
+>>> list(orig.flatten()) == list(reload.flatten())
+True
+
>>> orig = Instance.from_resource("berlin52")
+>>> text = []
+>>> orig.to_stream(text.append)
+>>> reload = _from_stream(iter(text),
+...     lambda _: orig.tour_length_lower_bound)
+>>> orig.n_cities == reload.n_cities
+True
+>>> orig.name == reload.name
+True
+>>> list(orig.flatten()) == list(reload.flatten())
+True
+
>>> orig = Instance.from_resource("ft53")
+>>> text = []
+>>> orig.to_stream(text.append)
+>>> reload = _from_stream(iter(text),
+...     lambda _: orig.tour_length_lower_bound)
+>>> orig.n_cities == reload.n_cities
+True
+>>> orig.name == reload.name
+True
+>>> list(orig.flatten()) == list(reload.flatten())
+True
+
tour_length_lower_bound: int

the lower bound of the tour length

tour_length_upper_bound: int

the upper bound of the tour length

moptipyapps.tsp.instance.ncities_from_tsplib_name(name)[source]

Compute the instance scale from the instance name.

Parameters:

name (str) – the instance name

Return type:

int

Returns:

the instance scale

moptipyapps.tsp.known_optima module

A set of tools to load known optima in the TSPLib format.

The TSPLib provides benchmark instances for the Traveling Salesperson Problem (TSP). We provide a subset of these instances with up to 2000 cities as resources in instance. Additionally, TSPLib provides the optimal solutions for a subset of these instances. Within this module here, we provide methods for reading the optimal solution files in the TSPLib format. We also include as resources the optimal solutions to the instances that our package provide as resources as well.

You can get the list of instance names for which the optimal tours are included in this package via list_resource_tours() and then load a tour from a resource via opt_tour_from_resource(). If you want to read a tour from an external file that obeys the TSPLib format, you can do so via opt_tour_from_file().

  1. Gerhard Reinelt. TSPLIB - A Traveling Salesman Problem Library. ORSA Journal on Computing 3(4):376-384. November 1991. https://doi.org/10.1287/ijoc.3.4.376. http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/

  2. Gerhard Reinelt. TSPLIB95. Heidelberg, Germany: Universität Heidelberg, Institut für Angewandte Mathematik. 1995. http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp95.pdf

moptipyapps.tsp.known_optima.list_resource_tours()[source]

Get a tuple of the names of the optimal tours in the resources.

>>> list_resource_tours()
+('a280', 'att48', 'bayg29', 'bays29', 'berlin52', 'brg180', 'ch130', 'ch150', 'cn11', 'eil101', 'eil51', 'eil76', 'fri26', 'gr120', 'gr202', 'gr24', 'gr48', 'gr666', 'gr96', 'kroA100', 'kroC100', 'kroD100', 'lin105', 'pcb442', 'pr1002', 'pr76', 'rd100', 'st70', 'tsp225', 'ulysses16', 'ulysses22')
+
Return type:

tuple[str, ...]

Returns:

the tuple

moptipyapps.tsp.known_optima.opt_tour_from_file(path)[source]

Read a known optimal tour from a file.

Parameters:

path (str) – the path to the file

Return type:

ndarray

Returns:

the tour

moptipyapps.tsp.known_optima.opt_tour_from_resource(name)[source]

Load an optimal tour from a resource.

>>> np.array2string(opt_tour_from_resource("ulysses16"))
+'[ 0 13 12 11  6  5 14  4 10  8  9 15  2  1  3  7]'
+
>>> np.array2string(opt_tour_from_resource("cn11"))
+'[ 0  2  3  6  9 10  5  8  7  1  4]'
+
Parameters:

name (str) – the name string

Return type:

ndarray

Returns:

the optimal tour

moptipyapps.tsp.tour_length module

The tour length objective function for tours in path representation.

A Traveling Salesperson Problem (TSP) instance is defined as a fully-connected graph with n_cities nodes. Each edge in the graph has a weight, which identifies the distance between the nodes. The goal is to find the shortest tour that visits every single node in the graph exactly once and then returns back to its starting node. Then nodes are usually called cities. In this file, we present methods for loading instances of the TSP as distance matrices A. In other words, the value at A[i, j] identifies the travel distance from i to j.

A tour can be represented in path representation, which means that it is stored as a permutation of the numbers 0 to n_cities-1. The number at index k identifies that k-th city to visit. So the first number in the permutation identifies the first city, the second number the second city, and so on.

The length of the tour can be computed by summing up the distances from the k-th city to the k+1-st city, for k in 0..n_cities-2 and then adding the distance from the last city to the first city. This is what the function tour_length() is doing. This function is then wrapped as objective function object in TourLength.

Important initial work on this code has been contributed by Mr. Tianyu LIANG (梁天宇), <liangty@stu.hfuu.edu.cn> a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

  1. Gerhard Reinelt. TSPLIB - A Traveling Salesman Problem Library. ORSA Journal on Computing 3(4):376-384. November 1991. https://doi.org/10.1287/ijoc.3.4.376. http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/

  2. Gerhard Reinelt. TSPLIB95. Heidelberg, Germany: Universität Heidelberg, Institut für Angewandte Mathematik. 1995. http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp95.pdf

  3. David Lee Applegate, Robert E. Bixby, Vašek Chvátal, and William John Cook. The Traveling Salesman Problem: A Computational Study. Second Edition, 2007. Princeton, NJ, USA: Princeton University Press. Volume 17 of Princeton Series in Applied Mathematics. ISBN: 0-691-12993-2.

  4. Gregory Z. Gutin and Abraham P. Punnen. The Traveling Salesman Problem and its Variations. 2002. Kluwer Academic Publishers. Volume 12 of Combinatorial Optimization. https://doi.org/10.1007/b101971.

  5. Pedro Larrañaga, Cindy M. H. Kuijpers, Roberto H. Murga, Iñaki Inza, and Sejla Dizdarevic. Genetic Algorithms for the Travelling Salesman Problem: A Review of Representations and Operators. Artificial Intelligence Review 13(2):129-170. April 1999. https://doi.org/10.1023/A:1006529012972.

  6. Eugene Leighton Lawler, Jan Karel Lenstra, Alexander Hendrik George Rinnooy Kan, and David B. Shmoys. The Traveling Salesman Problem: A Guided Tour of Combinatorial Optimization. September 1985. Chichester, West Sussex, UK: Wiley Interscience. In Estimation, Simulation, and Control - Wiley-Interscience Series in Discrete Mathematics and Optimization. ISBN: 0-471-90413-9

  7. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, and Thomas Weise. Solving the Traveling Salesperson Problem using Frequency Fitness Assignment. In Proceedings of the IEEE Symposium on Foundations of Computational Intelligence (IEEE FOCI’22), part of the IEEE Symposium Series on Computational Intelligence (SSCI’22), December 4-7, 2022, Singapore. Pages 360-367. IEEE. https://doi.org/10.1109/SSCI51031.2022.10022296.

  8. Thomas Weise, Raymond Chiong, Ke Tang, Jörg Lässig, Shigeyoshi Tsutsui, Wenxiang Chen, Zbigniew Michalewicz, and Xin Yao. Benchmarking Optimization Algorithms: An Open Source Framework for the Traveling Salesman Problem. IEEE Computational Intelligence Magazine. 9(3):40-52. August 2014. https://doi.org/10.1109/MCI.2014.2326101.

class moptipyapps.tsp.tour_length.TourLength(instance)[source]

Bases: Objective

The tour length objective function.

evaluate(x)[source]

Compute the length of a tour in path representation.

Parameters:

x – the tour in path representation

Return type:

int

Returns:

the tour length

instance: Final[Instance]

the TSP instance we are trying to solve

log_parameters_to(logger)[source]

Log the parameters of the space to the given logger.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

lower_bound()[source]

Get the lower bound of the tour length.

Return type:

int

Returns:

the lower bound of the tour length

upper_bound()[source]

Get the upper bound of the tour length.

Return type:

int

Returns:

the upper bound of the tour length

moptipyapps.tsp.tour_length.tour_length(instance, x)[source]

Compute the length of a tour.

Parameters:
Return type:

int

Returns:

the length of the tour x

diff --git a/moptipyapps.tsp.tsplib.html b/moptipyapps.tsp.tsplib.html new file mode 100644 index 00000000..e070e710 --- /dev/null +++ b/moptipyapps.tsp.tsplib.html @@ -0,0 +1 @@ +moptipyapps.tsp.tsplib package — moptipyapps 0.8.62 documentation

moptipyapps.tsp.tsplib package

The TSPLib example data for the Traveling Salesperson Problem (TSP).

This package does not offer anything useful except for holding the TSPLib files. You can find the documentation and actual classes for solving and playing around with the TSP in package tsp.

The original data of TSPLib can be found at <http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/>. Before doing anything with these data directly, you should make sure to read the FAQ <http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/TSPFAQ.html> and the documentation <http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp95.pdf>.

  1. Gerhard Reinelt. TSPLIB - A Traveling Salesman Problem Library. ORSA Journal on Computing 3(4):376-384. November 1991. https://doi.org/10.1287/ijoc.3.4.376. http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/

  2. Gerhard Reinelt. TSPLIB95. Heidelberg, Germany: Universität Heidelberg, Institut für Angewandte Mathematik. 1995. http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/tsp95.pdf

moptipyapps.tsp.tsplib.open_resource_stream(file_name)[source]

Open a TSPLib resource stream.

Parameters:

file_name (str) – the file name of the resource

Return type:

TextIO

Returns:

the stream

diff --git a/moptipyapps.ttp.html b/moptipyapps.ttp.html new file mode 100644 index 00000000..82390d4d --- /dev/null +++ b/moptipyapps.ttp.html @@ -0,0 +1,250 @@ +moptipyapps.ttp package — moptipyapps 0.8.62 documentation

moptipyapps.ttp package

The Traveling Tournament Problem (TTP).

The Traveling Tournament Problem (TTP) models the logistics of a sports league, where each game is defined as two teams playing against each other. In each time slot (let’s call that “day”) of the tournament, each team has one game against one other team. One of the two teams of each game will play “at home,” the other “away.”

In order to have each of the n teams play once against each of the n-1 other teams, we need to have n - 1 “days”. So in one round of the tournament, there are n - 1 time slots so that each of the teams can play exactly n - 1 games, i.e., once against every other team.

If the tournament has two rounds (i.e., is a double round-robin tournament), then each game appears twice, but with home and away team switched. There are also constraints for the minimum and maximum number of games at home in a row and away in a row. There are also constraints for the minimum and maximum number in between repeating a game (with home/away team switched).

  1. David Van Bulck. Minimum Travel Objective Repository. RobinX: An XML-driven Classification for Round-Robin Sports Timetabling. Faculty of Economics and Business Administration at Ghent University, Belgium. https://robinxval.ugent.be/. In this repository, you can also find the original instance data, useful descriptions, and many links to related works.

  2. Kelly Easton, George L. Nemhauser, and Michael K. Trick. The Traveling Tournament Problem Description and Benchmarks. In Principles and Practice of Constraint Programming (CP’01), November 26 - December 1, 2001, Paphos, Cyprus, pages 580-584, Berlin/Heidelberg, Germany: Springer. ISBN: 978-3-540-42863-3. https://doi.org/10.1007/3-540-45578-7_43. https://www.researchgate.net/publication/220270875.

  3. Celso C. Ribeiro and Sebastián Urrutia. Heuristics for the Mirrored Traveling Tournament Problem. European Journal of Operational Research (EJOR) 179(3):775-787, June 16, 2007. https://doi.org/10.1016/j.ejor.2005.03.061. https://optimization-online.org/wp-content/uploads/2004/04/851.pdf.

  4. Sebastián Urrutia and Celso C. Ribeiro. Maximizing Breaks and Bounding Solutions to the Mirrored Traveling Tournament Problem. Discrete Applied Mathematics 154(13):1932-1938, August 15, 2006. https://doi.org/10.1016/j.dam.2006.03.030.

So far, the following components have been implemented:

  1. instance provides the data of a TTP instance, including the number of teams, the constraints, and the distance matrix. Notice that it is an extension of the Traveling Salesperson Problem instance instance data, from which it inherits the distance matrix. This data basically describes the starting situation and the input data when we try to solve a TTP instance. Also, the module provides several of the benchmark instances from <https://robinxval.ugent.be/>.

  2. game_plan provides a class for holding one candidate solution to the TTP, i.e., a game plan. The game plan states, for each day and each team, against which other team it will plan (if any). The game plan may contain errors, which need to be sorted out by optimization.

  3. game_plan_space offers the moptipy Space functionality for such game plans. In other words, it allows us to instantiate game plans in a uniform way and to convert them to and from strings (which is used when writing log files).

  4. game_encoding allows us to decode a permutation (potentially with repetitions, see permutations) into a game plan. We therefore can use optimization algorithms and operators working on the well-understood space of permutations to produce game plans. However, the decoded game plans may have errors, e.g., slots without games or violations of the maximum or minimum streak length constraints.

  5. errors offers an objective function that counts the number of such constraint violations in a game plan. If we can minimize it to zero, then the game plan is feasible.

This is code is part of the research work of Mr. Xiang Cao (曹翔), a Master’s student at the Institute of Applied Optimization (应用优化研究所, http://iao.hfuu.edu.cn) of the School of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the supervision of Prof. Dr. Thomas Weise (汤卫思教授).

Subpackages

Submodules

moptipyapps.ttp.errors module

An objective that counts constraint violations.

The idea is that we will probably not be able to always produce game plans that adhere to all the constraints imposed by a Traveling Tournament Problem ttp instance, so we will instead probably usually generate game plans that may contain errors.

We will hope that optimization can take care of this by applying this objective function here to get rid of them. In the documentation of function count_errors(), we explain the different types of errors that may occur and that are counted.

This objective function plays thus well with encodings that produce infeasible schedules, such as the very simple game_encoding.

class moptipyapps.ttp.errors.Errors(instance)[source]

Bases: Objective

Compute the errors in a game plan.

This objective function encompasses all the constraints imposed on standard TTP instances in one summarizing number. See the documentation of count_errors() for more information.

evaluate(x)[source]

Count the errors in a game plan as objective value.

Parameters:

x (GamePlan) – the game plan

Return type:

int

Returns:

the number of errors in the plan

instance: Final[Instance]

the TTP instance

is_always_integer()[source]

State that this objective function is always integer-valued.

Return type:

bool

Returns:

True

lower_bound()[source]

Obtain the lower bound for errors: 0, which means error-free.

Return type:

int

Returns:

0

upper_bound()[source]

Compute upper bound for errors: (4*D - 1) * n - 1.

Here D is the number of days, n is the number of teams, and D = (n - 1) * rounds. See the documentation of count_errors().

Return type:

int

Returns:

(4*D - 1) * n - 1

moptipyapps.ttp.errors.count_errors(y, home_streak_min, home_streak_max, away_streak_min, away_streak_max, separation_min, separation_max, temp_1, temp_2)[source]

Compute the number of errors in a game plan.

This method counts the total number of the violations of any of the following constraints over D = (n - 1) * rounds days for n-team tournaments, where rounds == 2 for double-round robin. The following kinds of errors are counted:

  1. If a team A plays a team B on a given day, then B must play against A on that day and if A plays at home, then B must play away. If not, then that’s an error. This can result in at most D * n errors, because each of the n teams has (at most) one game on each of the D days and if the other team could play against the wrong team (or does not play at all), then that’s one error.

  2. If a team has no other team assigned to play with, this is designated by value 0 and causes 1 error. This error also ends all ongoing streaks, i.e., may additionally lead to a streak violation of at most max(home_streak_min, away_streak_min) - 1. However, this cannot be more then two errors in sum per day (minus 1, for the first day). Also, this error is mutually exclusive with error 1. This can result in at most D * n + (D - 1) * n = (2*D - 1) * n errors. Since this error is also mutually exclusive with the errors from constraints 3 to 8 below, this gives us an upper bound of (2*D - 1) * n errors for all of the constraints (1-8) together.

  3. A team has a home streak shorter than home_streak_min. No such error can occur on the first day. This error is mutually exclusive with error 2, as the streak violation is already counted there. This can result in at most (D - 1) * n errors, but this number is shared with the following constraints (4-6), because a streak can only either be a home streak or an away streak but not both and it can either be too short or too long, but not both.

  4. A team has a home streak longer than home_streak_max. No such error can occur on the first day. This error is mutually exclusive with error 2.

  5. A team has an away streak shorter than away_streak_min. No such error can occur on the first day. This error is mutually exclusive with error 2, as the streak violation is already counted there.

  6. A team has an away streak longer than away_streak_max. No such error can occur on the first day. This error is mutually exclusive with error 2.

  7. Team A plays team B again before the team has played at least separation_min games against other teams. This error cannot occur on the first day and is mutually exclusive with error 2. There can be most 1 such error per day for any pairing of teams and there are n//2 pairings per day, giving us an upper bound of D * n//2 errors in total. This error is mutually exclusive with the next constraint 8 (and constraint 2).

  8. Team A plays team B again after the team has played more than separation_max games against other teams. This error cannot occur on the first day and is mutually exclusive with error 2. There can be most 1 such error per day for any pairing of teams and there are n//2 pairings per day, giving us an upper bound of D * n//2 errors in total. This error is mutually exclusive with the previous constraint 7 (and constraint 2).

  9. If team A plays team B at home a times, then team B must play team A at home at least a-1 and at most a+1 times. In total, we have D*n games. There cannot be more than (D*n) - 1 such errors. Notice that this kind of error can never occur if we use our game_encoding as representation.

  10. Each pairing of teams occurs as same as often, namely rounds times, with rounds = 2 for double-round robin. In total, we have D*n games. There cannot be more than D*n such errors. Notice that this kind of error can never occur if we use our game_encoding as representation.

The violations are counted on a per-day basis. For example, if home_streak_max is 3 and a team has a home streak of length 5, then this counts as 2 errors. However, the errors are also counted in a non-redundant fashion: If a team A violates separation_min by, say, two days, then this counts as two errors. However, in this case, its opposing team B would have necessarily incured the exactly same violations. These are then not counted.

As upper bound for the number of errors, we therefore have to add those of constraints 2, 9, and 10 and get (2*D - 1) * n + D*n - 1 + D*n, which gives us (4*D - 1) * n - 1, where `D = (n - 1) * rounds. The lower bound is obviously 0.

Parameters:
  • y (ndarray) – the game plan

  • home_streak_min (int) – the minimum permitted home streak length

  • home_streak_max (int) – the maximum permitted home streak length

  • away_streak_min (int) – the minimum permitted away streak length

  • away_streak_max (int) – the maximum permitted away streak length

  • separation_min (int) – the minimum number of games between a repetition

  • separation_max (int) – the maximum number games between a repetition

  • temp_1 (ndarray) – a temporary n*(n-1)/2 integer array, which is used to hold, for each pairing, when the last game was played

  • temp_2 (ndarray) – a temporary n,n integer array, which is used to hold, how often each team played each other team

Return type:

int

Returns:

the total number of errors. 0 if the game plan is feasible

>>> count_errors(np.array([[-2, 1], [2, -1]], int),
+...              1, 3, 1, 3, 1, 2, np.empty(1, int),
+...              np.empty((2, 2), int))
+1
+>>> count_errors(np.array([[2, -1], [-2, 1]], int),
+...              1, 3, 1, 3, 1, 2, np.empty(1, int),
+...              np.empty((2, 2), int))
+1
+>>> count_errors(np.array([[ 2, -1,  4, -3],
+...                        [ 4,  3, -2, -1],
+...                        [-2,  1, -4,  3],
+...                        [-4, -3,  2,  1],
+...                        [ 3,  4, -1, -2],
+...                        [-3, -4,  1,  2]], int),
+...              1, 3, 1, 3, 1, 2, np.empty(6, int),
+...              np.empty((4, 4), int))
+2
+>>> count_errors(np.array([[ 2, -1,  4, -3],
+...                        [ 4,  3, -2, -1],
+...                        [-2,  1, -4,  3],
+...                        [ 3,  4, -1, -2],
+...                        [-4, -3,  2,  1],
+...                        [-3, -4,  1,  2]], int),
+...              1, 3, 1, 3, 1, 2, np.empty(6, int),
+...              np.empty((4, 4), int))
+0
+>>> count_errors(np.array([[ 2, -1,  4, -3],
+...                        [ 4,  3, -2, -1],
+...                        [ 3,  4, -1, -2],
+...                        [-2,  1, -4,  3],
+...                        [-4, -3,  2,  1],
+...                        [-3, -4,  1,  2]], int),
+...              1, 3, 1, 3, 1, 2, np.empty(6, int),
+...              np.empty((4, 4), int))
+0
+>>> count_errors(np.array([[ 2, -1,  4, -3],
+...                        [ 4,  3, -2, -1],
+...                        [ 3,  4, -1, -2],
+...                        [-2,  1, -4,  3],
+...                        [-4, -3,  2,  1],
+...                        [-3, -4,  1,  2]], int),
+...              1, 2, 1, 3, 1, 2, np.empty(6, int),
+...              np.empty((4, 4), int))
+3
+>>> count_errors(np.array([[ 2, -1,  4, -3],
+...                        [ 4,  3, -2, -1],
+...                        [ 3,  4, -1, -2],
+...                        [-2,  1, -4,  3],
+...                        [-4, -3,  2,  1],
+...                        [-3, -4,  1,  2]], int),
+...              1, 2, 1, 2, 1, 2, np.empty(6, int),
+...              np.empty((4, 4), int))
+6
+>>> count_errors(np.array([[ 2, -1,  4, -3],
+...                        [ 4,  3, -2, -1],
+...                        [ 3,  4, -1, -2],
+...                        [-2,  1, -4,  3],
+...                        [-4, -3,  2,  1],
+...                        [-3, -4,  1,  2]], int),
+...              1, 3, 1, 3, 1, 1, np.empty(6, int),
+...              np.empty((4, 4), int))
+6
+

moptipyapps.ttp.game_encoding module

A permutation-with-repetition-based encoding based on games.

A point in the search space is a permutation (potentially with repetitions) that can be translated to a GamePlan. Each value v in the permutation represents a game to be played by two of the n teams. There are n(n-1) possible games between n teams, distinguishing home and away teams. Given a value v from 0..n(n-1)-1, we can get the zero-based index of the home team as home_idx = (game // (n - 1)) % n. The away index is computed in two steps, first we set away_idx = game % (n - 1) and if away_idx >= home_idx, we do away_idx = away_idy + 1. (Because a team can never play against itself, the situation that home_idx == away_idx does not need to be represented, so we can “skip” over this possible value by doing the away_idx = away_idy + 1 and thus get a more “compact” numeric range for the permutation elements.)

A game schedule for any round-robin tournament with any given number of rounds can then be represented as permutation (potentially with repetitions) of these game values. In the decoding procedure, it is processed from beginning to end each game is then placed into the earliest slot not already occupied by another game. In other words, it is placed at the earliest day at which both involved teams do not yet have other games. If no such slot is available, this game is not placed at all. In this case, there will be some zeros in the game plan after the encoding. No other constraint is considered at this stage.

In other words, this encoding may produce game plans that violate constraints. It does not care about the streak length constraints. It does not ensure that each team always has a game. Therefore, it should only be used in conjunction with objective functions that force the search towards feasible solutions, such as the errors objective.

class moptipyapps.ttp.game_encoding.GameEncoding(instance)[source]

Bases: Encoding

An encoding that transforms strings of games to game plans.

instance: Final[Instance]

the instance

search_space()[source]

Create a proper search space for this game-based encoding.

The search space is a set of permutations that represents all the games that can take place in the tournament. Depending on the number of rounds in the tournament, some games may appear multiple times. Home and away games are distributed in a fair and deterministic mannner between the teams.

Return type:

Permutations

Returns:

the search space

>>> inst = Instance.from_resource("circ4")
+>>> inst.n_cities
+4
+>>> inst.rounds
+2
+>>> ";".join(map(str, GameEncoding(inst).search_space().blueprint))
+'0;1;2;3;4;5;6;7;8;9;10;11'
+>>> inst = Instance(inst.name, inst, inst.teams, inst.rounds,
+...                 inst.home_streak_min, inst.home_streak_max,
+...                 inst.away_streak_min, inst.away_streak_max,
+...                 inst.separation_min, inst.separation_max)
+>>> inst.rounds = 1  # modify number of rounds for copied instance
+>>> ";".join(map(str, GameEncoding(inst).search_space().blueprint))
+'1;2;3;7;8;10'
+>>> inst.rounds = 3  # modify number of rounds for copied instance
+>>> ";".join(map(str, GameEncoding(inst).search_space().blueprint))
+'0;1;1;2;2;3;3;4;5;6;7;7;8;8;9;10;10;11'
+>>> inst.rounds = 4  # modify number of rounds for copied instance
+>>> ";".join(map(str, GameEncoding(inst).search_space().blueprint))
+'0;0;1;1;2;2;3;3;4;4;5;5;6;6;7;7;8;8;9;9;10;10;11;11'
+>>> inst.rounds = 5  # modify number of rounds for copied instance
+>>> ";".join(map(str, GameEncoding(inst).search_space().blueprint))
+'0;0;1;1;1;2;2;2;3;3;3;4;4;5;5;6;6;7;7;7;8;8;8;9;9;10;10;10;11;11'
+
moptipyapps.ttp.game_encoding.map_games(x, y)[source]

Translate a permutation of games to a game plan.

This is a straightforward decoding that places the games into the map one by one. Each game is placed at the earliest slot in which it can be placed. If a game cannot be placed, it is ignored. This will lead to many errors, which can be counted via the errors objective.

Parameters:
  • x (ndarray) – the source permutation

  • y (ndarray) – the destination game plan

Return type:

None

>>> from moptipy.utils.nputils import int_range_to_dtype
+>>> teams = 2
+>>> rounds = 2
+>>> perm = search_space_for_n_and_rounds(teams, rounds).blueprint
+>>> dest = np.empty((rounds * (teams - 1), teams),
+...                 int_range_to_dtype(-teams, teams))
+>>> map_games(perm, dest)
+>>> print(dest)
+[[ 2 -1]
+ [-2  1]]
+>>> teams = 4
+>>> rounds = 2
+>>> perm = search_space_for_n_and_rounds(teams, rounds).blueprint
+>>> dest = np.empty((rounds * (teams - 1), teams),
+...                 int_range_to_dtype(-teams, teams))
+>>> map_games(perm, dest)
+>>> print(dest)
+[[ 2 -1  4 -3]
+ [ 3  4 -1 -2]
+ [ 4  3 -2 -1]
+ [-2  1 -4  3]
+ [-3 -4  1  2]
+ [-4 -3  2  1]]
+
moptipyapps.ttp.game_encoding.search_space_for_n_and_rounds(n, rounds)[source]

Create a proper search space for the given number of teams and rounds.

If the instance prescribes a double-round robin tournament, then this is just the standard() permutations set. Otherwise, it will be a permutation where some elements are omitted (for rounds == 1) or duplicated (if rounds > 2).

If an odd number of rounds is played, then it is not possible that all teams have the same number of games at home and away. Then, the permutation is generated such that, if the highest numbers of games at home for any team is k, no other team has less than k - 1 games at home. If the number of rounds is even, then all teams will have the same number of home and away games, that is, the number of teams divided by two and multiplied by the number of rounds.

Parameters:
  • n (int) – the number of teams

  • rounds (int) – the number of rounds

Return type:

Permutations

Returns:

the search space

>>> ";".join(map(str, search_space_for_n_and_rounds(2, 2).blueprint))
+'0;1'
+>>> ";".join(map(str, search_space_for_n_and_rounds(2, 3).blueprint))
+'0;1;1'
+>>> ";".join(map(str, search_space_for_n_and_rounds(2, 4).blueprint))
+'0;0;1;1'
+>>> ";".join(map(str, search_space_for_n_and_rounds(2, 5).blueprint))
+'0;0;1;1;1'
+>>> ";".join(map(str, search_space_for_n_and_rounds(3, 1).blueprint))
+'1;2;5'
+>>> ";".join(map(str, search_space_for_n_and_rounds(3, 2).blueprint))
+'0;1;2;3;4;5'
+>>> ";".join(map(str, search_space_for_n_and_rounds(3, 3).blueprint))
+'0;1;1;2;2;3;4;5;5'
+>>> ";".join(map(str, search_space_for_n_and_rounds(4, 1).blueprint))
+'1;2;3;7;8;10'
+>>> ";".join(map(str, search_space_for_n_and_rounds(4, 2).blueprint))
+'0;1;2;3;4;5;6;7;8;9;10;11'
+>>> ";".join(map(str, search_space_for_n_and_rounds(4, 3).blueprint))
+'0;1;1;2;2;3;3;4;5;6;7;7;8;8;9;10;10;11'
+>>> ";".join(map(str, search_space_for_n_and_rounds(4, 4).blueprint))
+'0;0;1;1;2;2;3;3;4;4;5;5;6;6;7;7;8;8;9;9;10;10;11;11'
+>>> ";".join(map(str, search_space_for_n_and_rounds(4, 5).blueprint))
+'0;0;1;1;1;2;2;2;3;3;3;4;4;5;5;6;6;7;7;7;8;8;8;9;9;10;10;10;11;11'
+>>> ";".join(map(str, search_space_for_n_and_rounds(5, 1).blueprint))
+'1;2;4;7;9;10;13;15;16;18'
+>>> ";".join(map(str, search_space_for_n_and_rounds(5, 2).blueprint))
+'0;1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;16;17;18;19'
+>>> ";".join(map(str, search_space_for_n_and_rounds(5, 3).blueprint))
+'0;1;1;2;2;3;4;4;5;6;7;7;8;9;9;10;10;11;12;13;13;14;15;15;16;16;17;18;18;19'
+

moptipyapps.ttp.game_plan module

A game plan assigns teams to games.

A game plan is a two-dimensional matrix G. The rows are the time slots. There is one column for each time. If G has value v at row i and column j, then this means:

  • at the time slot i

  • the team with name j+1 plays
    • no team if v == 0,

    • at home against the team v if v > 0, i.e., team v travels to the home stadium of team j+1

    • away against the team -v if v < 0, i.e., team j+1 travels to the home stadium of team -v and plays against them there

Indices in matrices are zero-based, i.e., the lowest index for a row i is 0 and the lowest index for a column j is also 0. However, team names are one-based, i.e., that with 1. Therefore, we need to translate the zero-based column index j to a team name by adding 1 to it.

This is just a numerical variant of the game plan representation given at <https://robinxval.ugent.be/RobinX/travelRepo.php>. Indeed, the str(…) representation of a game plan is exactly the table shown there.

Of course, if G[i, j] = v, then G[i, v - 1] = -(j + 1) should hold if v > 0, for example. Vice versa, if v < 0 and G[i, j] = v, then G[i, (-v) - 1] = j + 1 should hold. Such constraints are checked by the errors objective function.

The corresponding space implementation, game_plan_space, offers the functionality to convert strings to game plans as well as to instantiate them in a black-box algorithm.

class moptipyapps.ttp.game_plan.GamePlan(instance: Instance)[source]

Bases: Component, ndarray

A game plan, i.e., a solution to the Traveling Tournament Problem.

instance: Instance

the TTP instance

moptipyapps.ttp.game_plan_space module

Here we provide a Space of bin game plans.

The bin game plans object is defined in module game_plan. Basically, it is a two-dimensional numpy array holding, for each day (or time slot) for each team the opposing team.

class moptipyapps.ttp.game_plan_space.GamePlanSpace(instance)[source]

Bases: Space

An implementation of the Space API of for game plans.

create()[source]

Create a game plan without assigning items to locations.

Return type:

GamePlan

Returns:

the (empty, uninitialized) packing object

>>> inst = Instance.from_resource("circ8")
+>>> space = GamePlanSpace(inst)
+>>> x = space.create()
+>>> print(inst.rounds)
+2
+>>> print(inst.n_cities)
+8
+>>> x.shape
+(14, 8)
+>>> x.instance is inst
+True
+>>> type(x)
+<class 'moptipyapps.ttp.game_plan.GamePlan'>
+
from_str(text)[source]

Convert a string to a packing.

Parameters:

text (str) – the string

Return type:

GamePlan

Returns:

the packing

>>> inst = Instance.from_resource("circ6")
+>>> space = GamePlanSpace(inst)
+>>> y1 = space.create()
+>>> y1.fill(0)
+>>> y2 = space.from_str(space.to_str(y1))
+>>> space.is_equal(y1, y2)
+True
+>>> y1 is y2
+False
+
instance: Final[Instance]

The instance to which the packings apply.

is_equal(x1, x2)[source]

Check if two bin game plans have the same contents.

Parameters:
Return type:

bool

Returns:

True if both game plans are for the same instance and have the same structure

>>> inst = Instance.from_resource("circ4")
+>>> space = GamePlanSpace(inst)
+>>> y1 = space.create()
+>>> y1.fill(0)
+>>> y2 = space.create()
+>>> y2.fill(0)
+>>> space.is_equal(y1, y2)
+True
+>>> y1 is y2
+False
+>>> y1[0, 0] = 1
+>>> space.is_equal(y1, y2)
+False
+
log_parameters_to(logger)[source]

Log the parameters of the space to the given logger.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

n_points()[source]

Get the number of game plans.

The values in a game plan go from -n..n, including zero, and we have days*n values. This gives (2n + 1) ** (days * n), where days equals (n - 1) * rounds and rounds is the number of rounds. In total, this gives (2n + 1) ** ((n - 1) * rounds * n).

Return type:

int

Returns:

the number of possible game plans

>>> space = GamePlanSpace(Instance.from_resource("circ6"))
+>>> print((2 * 6 + 1) ** ((6 - 1) * 2 * 6))
+6864377172744689378196133203444067624537070830997366604446306636401
+>>> space.n_points()
+6864377172744689378196133203444067624537070830997366604446306636401
+>>> space = GamePlanSpace(Instance.from_resource("circ4"))
+>>> space.n_points()
+79766443076872509863361
+>>> print((2 * 4 + 1) ** ((4 - 1) * 2 * 4))
+79766443076872509863361
+
validate(x)[source]

Check if a game plan is an instance of the right object.

This method performs a superficial feasibility check, as in the TTP, we try to find feasible game plans and may have infeasible ones. All we check here is that the object is of the right type and dimensions and that it does not contain some out-of-bounds value.

Parameters:

x (GamePlan) – the game plan

Raises:
  • TypeError – if any component of the game plan is of the wrong type

  • ValueError – if the game plan is not feasible

Return type:

None

moptipyapps.ttp.instance module

An instance of the Traveling Tournament Problem (TTP).

The Traveling Tournament Problem (TTP) describes the logistics of a sports league. In this league, n teams compete. In each time slot, each team plays against one other team. In each game, one team plays at home and one team plays away with the other team. In each round, every team plays once against every other team. The league may have multiple rounds. If there are two rounds, then each team plays against each other team once at home and once abroad. If a team plays at home (or abroad) several times in a row, this is called a “streak”. There are minimum and maximum streak length constraints defined, for both at home and abroad. Additionally, if team A plays a team B in one time slot, then the exact inverse game cannot take place in the next time slot. A minimum number of games must take place in between for separation. There can also be a maximum separation length.

David Van Bulck of the Sports Scheduling Research group, part of the Faculty of Economics and Business Administration at Ghent University, Belgium, maintains “RobinX: An XML-driven Classification for Round-Robin Sports Timetabling”, a set of benchmark data instances and results of the TTP. We provide some of these instances as resources here. You can also download them directly at <https://robinxval.ugent.be/RobinX/travelRepo.php>. Also, see <https://robinxval.ugent.be/> for more information.

  1. David Van Bulck. Minimum Travel Objective Repository. RobinX: An XML-driven Classification for Round-Robin Sports Timetabling. Faculty of Economics and Business Administration at Ghent University, Belgium. https://robinxval.ugent.be/

  2. Kelly Easton, George L. Nemhauser, and Michael K. Trick. The Traveling Tournament Problem Description and Benchmarks. In Principles and Practice of Constraint Programming (CP’01), November 26 - December 1, 2001, Paphos, Cyprus, pages 580-584, Berlin/Heidelberg, Germany: Springer. ISBN: 978-3-540-42863-3. https://doi.org/10.1007/3-540-45578-7_43 https://www.researchgate.net/publication/220270875

class moptipyapps.ttp.instance.Instance(name: str, matrix: ndarray, teams: Iterable[str], rounds: int, home_streak_min: int, home_streak_max: int, away_streak_min: int, away_streak_max: int, separation_min: int, separation_max: int, tour_length_lower_bound: int = 0)[source]

Bases: Instance

An instance of Traveling Tournament Problem (TTP).

away_streak_max: int

the maximum number of games that can be played away in a row

away_streak_min: int

the minimum number of games that can be played away in a row

static from_file(path, lower_bound_getter=None)[source]

Read a TTP instance from a robinX formatted XML file.

Parameters:
Return type:

Instance

Returns:

the instance

>>> from os.path import dirname
+>>> inst = Instance.from_file(dirname(__file__) + "/robinx/con20.xml")
+>>> inst.name
+'con20'
+
static from_resource(name)[source]

Load a TTP instance from a resource.

Parameters:

name (str) – the name string

Return type:

Instance

Returns:

the instance

>>> insta = Instance.from_resource("bra24")
+>>> insta.n_cities
+24
+>>> insta.name
+'bra24'
+>>> insta.teams[0]
+'Atl.Mineiro'
+>>> insta.teams[1]
+'Atl.Paranaense'
+>>> insta.rounds
+2
+>>> insta.home_streak_min
+1
+>>> insta.home_streak_max
+3
+>>> insta.away_streak_min
+1
+>>> insta.away_streak_max
+3
+>>> insta.separation_min
+1
+>>> insta.separation_max
+46
+
game_plan_dtype: dtype

the data type to be used for plans

get_optimal_plan_length_bounds()[source]

Get lower and upper bounds in which the optimal plan length resides.

These are the bounds for the optimal tour length of feasible solutions. If we know the feasible solution with the smallest possible tour length, then the game_plan objective function would return a value within these limits for this solution. The limits for the RobinX instance have been taken from https://robinxval.ugent.be/RobinX/travelRepo.php on 2024-05-10.

Return type:

tuple[int, int]

Returns:

a tuple of the lower and upper limit for the optimal plan length

home_streak_max: int

the maximum number of games that can be played at home in a row

home_streak_min: int

the minimum number of games that can be played at home in a row

static list_resources(symmetric=True, asymmetric=True)[source]

Get a tuple of all the TTP instances available as resource.

All instances of the robinX set provided here are symmetric.

Parameters:
  • symmetric (bool, default: True) – include the instances with symmetric distance matrices

  • asymmetric (bool, default: True) – include the asymmetric instances with asymmetric distance matrices

Return type:

tuple[str, ...]

Returns:

the tuple with the instance names

>>> len(Instance.list_resources())
+118
+>>> len(Instance.list_resources(False, True))
+0
+>>> len(Instance.list_resources(True, False))
+118
+
log_parameters_to(logger)[source]

Log the parameters of the instance to the given logger.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

>>> from moptipy.utils.logger import InMemoryLogger
+>>> with InMemoryLogger() as l:
+...     with l.key_values("I") as kv:
+...         Instance.from_resource("gal4").log_parameters_to(kv)
+...     print(repr('@'.join(l.get_log())))
+'BEGIN_I@name: gal4@class: moptipyapps.ttp.instance.Instance@nCities: 4@tourLengthLowerBound: 67@tourLengthUpperBound: 160@symmetric: T@dtype: h@rounds: 2@homeStreakMin: 1@homeStreakMax: 3@awayStreakMin: 1@awayStreakMax: 3@separationMin: 1@separationMax: 6@gamePlanDtype: b@END_I'
+
rounds: int

the number of rounds

separation_max: int

the maximum number of games between a repetition of a game setup

separation_min: int

the minimum number of games between a repetition of a game setup

teams: tuple[str, ...]

the names of the teams

moptipyapps.ttp.plan_length module

An objective computing the total travel length of all teams in a game plan.

This objective function takes a game plan as argument and computes the total travel length for all teams. A game_plan is basically a matrix that, for each day (first dimension) stores against which each team (second dimension) plays. If a team plays at home (has a home game), its opponent is stored as a positive number. If the team has an away game, i.e., needs to visit the opponent, then this opponent is stored as a negative number. Team IDs go from 1 to n, i.e., a value in 1..n indicates a home game and a value in -n..-1 indicates an away game. The value 0 denotes a bye, i.e., that no game is scheduled for a team at the given day.

The total game plan length is computed as follows:

  1. the total length = 0

  2. for each team,
    1. start at the current location = home

    2. for each day,
      1. if the opponent number is negative, the next location is the opponent’s hometown;

      2. else if the opponent number is positive, the next location is the own hometown;

      3. else (if the opponent number is 0): add the bye penalty to the total length and jump to the next iteration

      4. add the distance from the current to the next location to the total length

      5. set the current location = the next location

    3. if the current location != own hometown, add the travel distance back from the current location to the hometown to the total travel length.

As penalty for the bye situation where no game is scheduled, we use twice the maximum distance between any two teams plus 1. The logic is that if a bye (i.e., a 0) inserted into a game plan, it replaces one game. Since it replaces one game, it affects up to two travels, namely from the previous location to the game location and from the game location to the next location. So the optimization process could sneakily try to cut longer legs of the tournament by inserting a bye. The longest imaginable travel would be between the two cities that are farthest away from each other and back. By making the penalty for a bye exactly one distance unit longer than this longest imaginable distance, we ensure that the travel length can never be reduced by inserting a bye. Thus, having a bye is always more costly than any other travel it could replace.

class moptipyapps.ttp.plan_length.GamePlanLength(instance)[source]

Bases: Objective

Compute the total travel length of a game plan.

This objective function sums up all the travel lengths over all teams. Days without game (bye) are penalized.

bye_penalty: Final[int]

the bye penalty

evaluate(x)[source]

Count the errors in a game plan as objective value.

Parameters:

x (GamePlan) – the game plan

Return type:

int

Returns:

the number of errors in the plan

instance: Final[Instance]

the TTP instance

is_always_integer()[source]

State that this objective function is always integer-valued.

Return type:

bool

Returns:

True

log_parameters_to(logger)[source]

Log the parameters of the instance to the given logger.

Parameters:

logger (KeyValueLogSection) – the logger for the parameters

Return type:

None

lower_bound()[source]

Obtain the lower bound for the travel length.

Return type:

int

Returns:

0

upper_bound()[source]

Compute upper bound for the travel length: All n*days*bye_penalty.

Return type:

int

Returns:

n * days * self.bye_penalty

moptipyapps.ttp.plan_length.game_plan_length(y, distances, bye_penalty)[source]

Compute the total travel length of a game plan.

Parameters:
  • y (ndarray) – the game plan

  • distances (ndarray) – the distance matrix

  • bye_penalty (int) – the penalty for bye = 0 entries, i.e., days where no game is scheduled

Return type:

int

Returns:

the total plan length

>>> yy = np.array([[ 2, -1,  4, -3],
+...                [-2,  1, -4,  3],
+...                [ 3,  4, -1, -2],
+...                [-3, -4,  1,  2],
+...                [ 4,  3, -2, -1],
+...                [-4, -3,  2,  1]], int)
+>>> dd = np.array([[ 0,  1,  2,  3],
+...                [ 7,  0,  4,  5],
+...                [ 8, 10,  0,  6],
+...                [ 9, 11, 12,  0]], int)
+>>> 0 + 1 + 7 + 2 + 8 + 3 + 9  # team 1
+30
+>>> 7 + 1 + 0 + 5 + 11 + 4 + 10  # team 2
+38
+>>> 0 + 6 + 9 + 2 + 10 + 4  # team 3
+31
+>>> 12 + 6 + 11 + 5 + 9 + 3  # team 4
+46
+>>> 30 + 38 + 31 + 46  # total sum
+145
+>>> game_plan_length(yy, dd, 0)
+145
+
>>> yy[1, 0] = 0  # add a bye
+>>> 0 + 25 + 0 + 2 + 8 + 3 + 9  # team 1
+47
+>>> game_plan_length(yy, dd, 2 * 12 + 1)
+162
+
diff --git a/moptipyapps.ttp.robinx.html b/moptipyapps.ttp.robinx.html new file mode 100644 index 00000000..43219fd3 --- /dev/null +++ b/moptipyapps.ttp.robinx.html @@ -0,0 +1 @@ +moptipyapps.ttp.robinx package — moptipyapps 0.8.62 documentation

moptipyapps.ttp.robinx package

The RobinX example data for the Traveling Tournament Problem (TTP).

David Van Bulck of the Sports Scheduling Research group, part of the Faculty of Economics and Business Administration at Ghent University, Belgium, maintains “RobinX: An XML-driven Classification for Round-Robin Sports Timetabling”, a set of benchmark data instances and results of the TTP. Here we include some of these TTP instances into our package.

This package does not offer anything useful except for holding the TTP files. You can find the documentation and actual classes for solving and playing around with the TSP in package ttp.

The original data of robinX can be found at <https://robinxval.ugent.be/RobinX/travelRepo.php>

moptipyapps.ttp.robinx.open_resource_stream(file_name)[source]

Open a RobinX resource stream.

Parameters:

file_name (str) – the file name of the resource

Return type:

TextIO

Returns:

the stream

diff --git a/objects.inv b/objects.inv new file mode 100644 index 00000000..3f764f38 Binary files /dev/null and b/objects.inv differ diff --git a/py-modindex.html b/py-modindex.html new file mode 100644 index 00000000..51cce7d4 --- /dev/null +++ b/py-modindex.html @@ -0,0 +1 @@ +Python Module Index — moptipyapps 0.8.62 documentation

Python Module Index

 
m
moptipyapps
    moptipyapps.binpacking2d
    moptipyapps.binpacking2d.bks
    moptipyapps.binpacking2d.encodings
    moptipyapps.binpacking2d.encodings.ibl_encoding_1
    moptipyapps.binpacking2d.encodings.ibl_encoding_2
    moptipyapps.binpacking2d.experiment
    moptipyapps.binpacking2d.instance
    moptipyapps.binpacking2d.instgen
    moptipyapps.binpacking2d.instgen.errors
    moptipyapps.binpacking2d.instgen.errors_and_hardness
    moptipyapps.binpacking2d.instgen.experiment
    moptipyapps.binpacking2d.instgen.hardness
    moptipyapps.binpacking2d.instgen.inst_decoding
    moptipyapps.binpacking2d.instgen.instance_space
    moptipyapps.binpacking2d.instgen.problem
    moptipyapps.binpacking2d.make_instances
    moptipyapps.binpacking2d.objectives
    moptipyapps.binpacking2d.objectives.bin_count
    moptipyapps.binpacking2d.objectives.bin_count_and_empty
    moptipyapps.binpacking2d.objectives.bin_count_and_last_empty
    moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline
    moptipyapps.binpacking2d.objectives.bin_count_and_last_small
    moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline
    moptipyapps.binpacking2d.objectives.bin_count_and_small
    moptipyapps.binpacking2d.packing
    moptipyapps.binpacking2d.packing_result
    moptipyapps.binpacking2d.packing_space
    moptipyapps.binpacking2d.packing_statistics
    moptipyapps.binpacking2d.plot_packing
    moptipyapps.dynamic_control
    moptipyapps.dynamic_control.controller
    moptipyapps.dynamic_control.controllers
    moptipyapps.dynamic_control.controllers.ann
    moptipyapps.dynamic_control.controllers.codegen
    moptipyapps.dynamic_control.controllers.cubic
    moptipyapps.dynamic_control.controllers.linear
    moptipyapps.dynamic_control.controllers.min_ann
    moptipyapps.dynamic_control.controllers.partially_linear
    moptipyapps.dynamic_control.controllers.peaks
    moptipyapps.dynamic_control.controllers.predefined
    moptipyapps.dynamic_control.controllers.quadratic
    moptipyapps.dynamic_control.experiment_raw
    moptipyapps.dynamic_control.experiment_surrogate
    moptipyapps.dynamic_control.instance
    moptipyapps.dynamic_control.model_objective
    moptipyapps.dynamic_control.objective
    moptipyapps.dynamic_control.ode
    moptipyapps.dynamic_control.results_log
    moptipyapps.dynamic_control.results_plot
    moptipyapps.dynamic_control.starting_points
    moptipyapps.dynamic_control.surrogate_optimizer
    moptipyapps.dynamic_control.system
    moptipyapps.dynamic_control.system_model
    moptipyapps.dynamic_control.systems
    moptipyapps.dynamic_control.systems.lorenz
    moptipyapps.dynamic_control.systems.stuart_landau
    moptipyapps.dynamic_control.systems.three_coupled_oscillators
    moptipyapps.order1d
    moptipyapps.order1d.distances
    moptipyapps.order1d.instance
    moptipyapps.order1d.space
    moptipyapps.qap
    moptipyapps.qap.instance
    moptipyapps.qap.objective
    moptipyapps.qap.qaplib
    moptipyapps.shared
    moptipyapps.tests
    moptipyapps.tests.on_binpacking2d
    moptipyapps.tests.on_tsp
    moptipyapps.tsp
    moptipyapps.tsp.ea1p1_revn
    moptipyapps.tsp.fea1p1_revn
    moptipyapps.tsp.instance
    moptipyapps.tsp.known_optima
    moptipyapps.tsp.tour_length
    moptipyapps.tsp.tsplib
    moptipyapps.ttp
    moptipyapps.ttp.errors
    moptipyapps.ttp.game_encoding
    moptipyapps.ttp.game_plan
    moptipyapps.ttp.game_plan_space
    moptipyapps.ttp.instance
    moptipyapps.ttp.plan_length
    moptipyapps.ttp.robinx
    moptipyapps.version
diff --git a/pyproject_toml.html b/pyproject_toml.html new file mode 100644 index 00000000..cef426db --- /dev/null +++ b/pyproject_toml.html @@ -0,0 +1,5 @@ +

[build-system]
+requires = ["setuptools>=75.5.0"]
+build-backend = "setuptools.build_meta"
+
diff --git a/requirements-dev_txt.html b/requirements-dev_txt.html new file mode 100644 index 00000000..740660b1 --- /dev/null +++ b/requirements-dev_txt.html @@ -0,0 +1,38 @@ +

#
+# Dependencies Required to Develop `moptipyapps`
+#
+# In this file we list all the libraries that are required to develop
+# `moptipyapps` code. This means that you want to run the complete `make`
+# process, including all the unit tests, the static analysis, and building the
+# documentation. Notice that none of this is necessary if you just want to use
+# `moptipyapps` or even just want to implement an algorithm or problem for your
+# own purposes. Only if you want to do all the checks and the whole build
+# process, maybe because you want to submit code to the `moptipy` project,
+# then you need all of these dependencies. For only using `moptipy`, it is
+# sufficient to load the dependencies specified in `requirements.txt`.
+# Nevertheless, I think even then it may still be a good idea to use the full
+# `make` process, because the static analysis and unit tests are, indeed,
+# useful. They enforce a uniform coding style, help you to find bugs, and kind
+# of push you to use best practices. I personally learned a lot from the
+# static analysis and the unit tests indeed revealed errors.
+#
+# For developing or contributing to `moptipyapps`, you should install the
+# dependencies below. You can do this via
+# `pip install --no-input --timeout 360 --retries 100 -r requirements-dev.txt`
+# with a text file `requirements-dev.txt` that has the exact same contents as
+# this file here.
+#
+# `moptipyapps` is available at https://thomasweise.github.io/moptipyapps.
+# This file is available at https://github.com/thomasWeise/moptipyapps.
+#
+# Below, we list each library required for development and specify the reason
+# why it is needed. Notice that these libraries may, in turn, depend on other
+# libraries. We cannot maintain a full list of these recursive dependencies.
+# Here we only list the top-level dependencies that are actually used in the
+# `moptipyapps` build process.
+#
+
+# pycommons provides lots of utilities
+pycommons[dev] >= 0.8.58
+
diff --git a/requirements_txt.html b/requirements_txt.html new file mode 100644 index 00000000..dcd6bc7e --- /dev/null +++ b/requirements_txt.html @@ -0,0 +1,51 @@ +

#
+# Dependencies Required to Use `moptipyapps`
+#
+# In this file we list all the libraries that are required to use
+# `moptipyapps`. You must install them before using `moptipyapps`.
+# If you install `moptipyapps` via pip, i.e., do
+# `pip install moptipyapps`, then this is done automatically for you.
+# Otherwise, you could do
+# `pip install --no-input --timeout 360 --retries 100 -r requirements.txt`
+# with a text file `requirements.txt` that has the exact same contents as this
+# file here.
+#
+# `moptipyapps` is available at https://thomasweise.github.io/moptipyapps.
+# This file is available at https://github.com/thomasWeise/moptipyapps.
+#
+# Below, we list each required library and specify the reason why it is
+# needed. Notice that these libraries may, in turn, depend on other libraries.
+# We cannot maintain a full list of these recursive dependencies.
+# Here we only list the top-level dependencies that are actually used by
+# `moptipyapps` directly.
+#
+
+# `moptipy` provides the basic optimization infrastructure and the spaces and
+# tools that we use for optimization.
+moptipy == 0.9.136
+
+# the common tools package
+pycommons == 0.8.58
+
+# `numpy` is needed for its efficient data structures.
+numpy == 1.26.4
+
+# numba provides JIT compilers useful making vector/numpy operations efficient
+numba == 0.60.0
+
+# matplotlib is used to create plots in the evaluation procedures.
+matplotlib == 3.9.2
+
+# scipy is used, e.g., for integrating systems of differential equations.
+scipy == 1.14.1
+
+# urllib3 and certifi are used to build instance data from internet resources.
+# They are also used to check the URLs in the README.md as part of the build
+# process, we check all the URLs in the README.md file..
+urllib3 == 2.2.3
+certifi == 2024.8.30
+
+# defusedxml is used as safe alternative for XML parsing the TTP data
+defusedxml == 0.7.1
+
diff --git a/search.html b/search.html new file mode 100644 index 00000000..2e20e4d2 --- /dev/null +++ b/search.html @@ -0,0 +1 @@ +Search — moptipyapps 0.8.62 documentation

Search

Searching for multiple words only shows matches that contain all words.

diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 00000000..56e6defa --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"1. Introduction": [[0, null], [1, "introduction"]], "2. Installation": [[0, "installation"], [1, "installation"]], "3. Applications": [[0, "applications"], [1, "applications"]], "3.1. Two-Dimensional Bin Packing Problem": [[0, "two-dimensional-bin-packing-problem"], [1, "two-dimensional-bin-packing-problem"]], "3.2. The Traveling Salesperson Problem (TSP)": [[0, "the-traveling-salesperson-problem-tsp"], [1, "the-traveling-salesperson-problem-tsp"]], "3.3. Dynamic Controller Synthesis": [[0, "dynamic-controller-synthesis"], [1, "dynamic-controller-synthesis"]], "3.4. The Traveling Tournament Problem (TTP)": [[0, "the-traveling-tournament-problem-ttp"], [1, "the-traveling-tournament-problem-ttp"]], "3.5. The Quadratic Assignment Problem (QAP)": [[0, "the-quadratic-assignment-problem-qap"], [1, "the-quadratic-assignment-problem-qap"]], "3.6. One-Dimensional Ordering": [[0, "one-dimensional-ordering"], [1, "one-dimensional-ordering"]], "4. Unit Tests and Static Analysis": [[0, "unit-tests-and-static-analysis"], [1, "unit-tests-and-static-analysis"]], "5. License": [[0, "license"], [1, "license"]], "5.1. Exceptions": [[0, "exceptions"], [1, "exceptions"]], "6. Contact": [[0, "contact"], [1, "contact"]], "7. Modules and Code": [[1, "modules-and-code"]], "Submodules": [[3, "submodules"], [4, "submodules"], [5, "submodules"], [6, "submodules"], [7, "submodules"], [8, "submodules"], [9, "submodules"], [10, "submodules"], [11, "submodules"], [12, "submodules"], [14, "submodules"], [15, "submodules"], [17, "submodules"]], "Subpackages": [[3, "subpackages"], [4, "subpackages"], [8, "subpackages"], [12, "subpackages"], [15, "subpackages"], [17, "subpackages"]], "moptipyapps": [[2, null]], "moptipyapps package": [[3, null]], "moptipyapps.binpacking2d package": [[4, null]], "moptipyapps.binpacking2d.bks module": [[4, "module-moptipyapps.binpacking2d.bks"]], "moptipyapps.binpacking2d.encodings package": [[5, null]], "moptipyapps.binpacking2d.encodings.ibl_encoding_1 module": [[5, "module-moptipyapps.binpacking2d.encodings.ibl_encoding_1"]], "moptipyapps.binpacking2d.encodings.ibl_encoding_2 module": [[5, "module-moptipyapps.binpacking2d.encodings.ibl_encoding_2"]], "moptipyapps.binpacking2d.experiment module": [[4, "module-moptipyapps.binpacking2d.experiment"]], "moptipyapps.binpacking2d.instance module": [[4, "module-moptipyapps.binpacking2d.instance"]], "moptipyapps.binpacking2d.instgen package": [[6, null]], "moptipyapps.binpacking2d.instgen.errors module": [[6, "module-moptipyapps.binpacking2d.instgen.errors"]], "moptipyapps.binpacking2d.instgen.errors_and_hardness module": [[6, "module-moptipyapps.binpacking2d.instgen.errors_and_hardness"]], "moptipyapps.binpacking2d.instgen.experiment module": [[6, "module-moptipyapps.binpacking2d.instgen.experiment"]], "moptipyapps.binpacking2d.instgen.hardness module": [[6, "module-moptipyapps.binpacking2d.instgen.hardness"]], "moptipyapps.binpacking2d.instgen.inst_decoding module": [[6, "module-moptipyapps.binpacking2d.instgen.inst_decoding"]], "moptipyapps.binpacking2d.instgen.instance_space module": [[6, "module-moptipyapps.binpacking2d.instgen.instance_space"]], "moptipyapps.binpacking2d.instgen.problem module": [[6, "module-moptipyapps.binpacking2d.instgen.problem"]], "moptipyapps.binpacking2d.make_instances module": [[4, "module-moptipyapps.binpacking2d.make_instances"]], "moptipyapps.binpacking2d.objectives package": [[7, null]], "moptipyapps.binpacking2d.objectives.bin_count module": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count"]], "moptipyapps.binpacking2d.objectives.bin_count_and_empty module": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_empty"]], "moptipyapps.binpacking2d.objectives.bin_count_and_last_empty module": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_last_empty"]], "moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline module": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline"]], "moptipyapps.binpacking2d.objectives.bin_count_and_last_small module": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_last_small"]], "moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline module": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline"]], "moptipyapps.binpacking2d.objectives.bin_count_and_small module": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_small"]], "moptipyapps.binpacking2d.packing module": [[4, "module-moptipyapps.binpacking2d.packing"]], "moptipyapps.binpacking2d.packing_result module": [[4, "module-moptipyapps.binpacking2d.packing_result"]], "moptipyapps.binpacking2d.packing_space module": [[4, "module-moptipyapps.binpacking2d.packing_space"]], "moptipyapps.binpacking2d.packing_statistics module": [[4, "module-moptipyapps.binpacking2d.packing_statistics"]], "moptipyapps.binpacking2d.plot_packing module": [[4, "module-moptipyapps.binpacking2d.plot_packing"]], "moptipyapps.dynamic_control package": [[8, null]], "moptipyapps.dynamic_control.controller module": [[8, "module-moptipyapps.dynamic_control.controller"]], "moptipyapps.dynamic_control.controllers package": [[9, null]], "moptipyapps.dynamic_control.controllers.ann module": [[9, "module-moptipyapps.dynamic_control.controllers.ann"]], "moptipyapps.dynamic_control.controllers.codegen module": [[9, "module-moptipyapps.dynamic_control.controllers.codegen"]], "moptipyapps.dynamic_control.controllers.cubic module": [[9, "module-moptipyapps.dynamic_control.controllers.cubic"]], "moptipyapps.dynamic_control.controllers.linear module": [[9, "module-moptipyapps.dynamic_control.controllers.linear"]], "moptipyapps.dynamic_control.controllers.min_ann module": [[9, "module-moptipyapps.dynamic_control.controllers.min_ann"]], "moptipyapps.dynamic_control.controllers.partially_linear module": [[9, "module-moptipyapps.dynamic_control.controllers.partially_linear"]], "moptipyapps.dynamic_control.controllers.peaks module": [[9, "module-moptipyapps.dynamic_control.controllers.peaks"]], "moptipyapps.dynamic_control.controllers.predefined module": [[9, "module-moptipyapps.dynamic_control.controllers.predefined"]], "moptipyapps.dynamic_control.controllers.quadratic module": [[9, "module-moptipyapps.dynamic_control.controllers.quadratic"]], "moptipyapps.dynamic_control.experiment_raw module": [[8, "module-moptipyapps.dynamic_control.experiment_raw"]], "moptipyapps.dynamic_control.experiment_surrogate module": [[8, "module-moptipyapps.dynamic_control.experiment_surrogate"]], "moptipyapps.dynamic_control.instance module": [[8, "module-moptipyapps.dynamic_control.instance"]], "moptipyapps.dynamic_control.model_objective module": [[8, "module-moptipyapps.dynamic_control.model_objective"]], "moptipyapps.dynamic_control.objective module": [[8, "module-moptipyapps.dynamic_control.objective"]], "moptipyapps.dynamic_control.ode module": [[8, "module-moptipyapps.dynamic_control.ode"]], "moptipyapps.dynamic_control.results_log module": [[8, "module-moptipyapps.dynamic_control.results_log"]], "moptipyapps.dynamic_control.results_plot module": [[8, "module-moptipyapps.dynamic_control.results_plot"]], "moptipyapps.dynamic_control.starting_points module": [[8, "module-moptipyapps.dynamic_control.starting_points"]], "moptipyapps.dynamic_control.surrogate_optimizer module": [[8, "module-moptipyapps.dynamic_control.surrogate_optimizer"]], "moptipyapps.dynamic_control.system module": [[8, "module-moptipyapps.dynamic_control.system"]], "moptipyapps.dynamic_control.system_model module": [[8, "module-moptipyapps.dynamic_control.system_model"]], "moptipyapps.dynamic_control.systems package": [[10, null]], "moptipyapps.dynamic_control.systems.lorenz module": [[10, "module-moptipyapps.dynamic_control.systems.lorenz"]], "moptipyapps.dynamic_control.systems.stuart_landau module": [[10, "module-moptipyapps.dynamic_control.systems.stuart_landau"]], "moptipyapps.dynamic_control.systems.three_coupled_oscillators module": [[10, "module-moptipyapps.dynamic_control.systems.three_coupled_oscillators"]], "moptipyapps.order1d package": [[11, null]], "moptipyapps.order1d.distances module": [[11, "module-moptipyapps.order1d.distances"]], "moptipyapps.order1d.instance module": [[11, "module-moptipyapps.order1d.instance"]], "moptipyapps.order1d.space module": [[11, "module-moptipyapps.order1d.space"]], "moptipyapps.qap package": [[12, null]], "moptipyapps.qap.instance module": [[12, "module-moptipyapps.qap.instance"]], "moptipyapps.qap.objective module": [[12, "module-moptipyapps.qap.objective"]], "moptipyapps.qap.qaplib package": [[13, null]], "moptipyapps.shared module": [[3, "module-moptipyapps.shared"]], "moptipyapps.tests package": [[14, null]], "moptipyapps.tests.on_binpacking2d module": [[14, "module-moptipyapps.tests.on_binpacking2d"]], "moptipyapps.tests.on_tsp module": [[14, "module-moptipyapps.tests.on_tsp"]], "moptipyapps.tsp package": [[15, null]], "moptipyapps.tsp.ea1p1_revn module": [[15, "module-moptipyapps.tsp.ea1p1_revn"]], "moptipyapps.tsp.fea1p1_revn module": [[15, "module-moptipyapps.tsp.fea1p1_revn"]], "moptipyapps.tsp.instance module": [[15, "module-moptipyapps.tsp.instance"]], "moptipyapps.tsp.known_optima module": [[15, "module-moptipyapps.tsp.known_optima"]], "moptipyapps.tsp.tour_length module": [[15, "module-moptipyapps.tsp.tour_length"]], "moptipyapps.tsp.tsplib package": [[16, null]], "moptipyapps.ttp package": [[17, null]], "moptipyapps.ttp.errors module": [[17, "module-moptipyapps.ttp.errors"]], "moptipyapps.ttp.game_encoding module": [[17, "module-moptipyapps.ttp.game_encoding"]], "moptipyapps.ttp.game_plan module": [[17, "module-moptipyapps.ttp.game_plan"]], "moptipyapps.ttp.game_plan_space module": [[17, "module-moptipyapps.ttp.game_plan_space"]], "moptipyapps.ttp.instance module": [[17, "module-moptipyapps.ttp.instance"]], "moptipyapps.ttp.plan_length module": [[17, "module-moptipyapps.ttp.plan_length"]], "moptipyapps.ttp.robinx package": [[18, null]], "moptipyapps.version module": [[3, "module-moptipyapps.version"]], "moptipyapps: Applications of Metaheuristic Optimization in Python": [[1, null]]}, "docnames": ["README", "index", "modules", "moptipyapps", "moptipyapps.binpacking2d", "moptipyapps.binpacking2d.encodings", "moptipyapps.binpacking2d.instgen", "moptipyapps.binpacking2d.objectives", "moptipyapps.dynamic_control", "moptipyapps.dynamic_control.controllers", "moptipyapps.dynamic_control.systems", "moptipyapps.order1d", "moptipyapps.qap", "moptipyapps.qap.qaplib", "moptipyapps.tests", "moptipyapps.tsp", "moptipyapps.tsp.tsplib", "moptipyapps.ttp", "moptipyapps.ttp.robinx"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["README.md", "index.rst", "modules.rst", "moptipyapps.rst", "moptipyapps.binpacking2d.rst", "moptipyapps.binpacking2d.encodings.rst", "moptipyapps.binpacking2d.instgen.rst", "moptipyapps.binpacking2d.objectives.rst", "moptipyapps.dynamic_control.rst", "moptipyapps.dynamic_control.controllers.rst", "moptipyapps.dynamic_control.systems.rst", "moptipyapps.order1d.rst", "moptipyapps.qap.rst", "moptipyapps.qap.qaplib.rst", "moptipyapps.tests.rst", "moptipyapps.tsp.rst", "moptipyapps.tsp.tsplib.rst", "moptipyapps.ttp.rst", "moptipyapps.ttp.robinx.rst"], "indexentries": {"a_large (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.A_LARGE", false]], "a_med (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.A_MED", false]], "a_small (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.A_SMALL", false]], "anns() (in module moptipyapps.dynamic_control.controllers.ann)": [[9, "moptipyapps.dynamic_control.controllers.ann.anns", false]], "append_almost_squares_strings() (in module moptipyapps.binpacking2d.make_instances)": [[4, "moptipyapps.binpacking2d.make_instances.append_almost_squares_strings", false]], "asqas (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.ASQAS", false]], "away_streak_max (moptipyapps.ttp.instance.instance attribute)": [[17, "moptipyapps.ttp.instance.Instance.away_streak_max", false]], "away_streak_min (moptipyapps.ttp.instance.instance attribute)": [[17, "moptipyapps.ttp.instance.Instance.away_streak_min", false]], "base_setup() (in module moptipyapps.binpacking2d.experiment)": [[4, "moptipyapps.binpacking2d.experiment.base_setup", false]], "base_setup() (in module moptipyapps.dynamic_control.experiment_raw)": [[8, "moptipyapps.dynamic_control.experiment_raw.base_setup", false]], "base_setup() (in module moptipyapps.dynamic_control.experiment_surrogate)": [[8, "moptipyapps.dynamic_control.experiment_surrogate.base_setup", false]], "begin() (moptipyapps.dynamic_control.model_objective.modelobjective method)": [[8, "moptipyapps.dynamic_control.model_objective.ModelObjective.begin", false]], "beng_1_8 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.BENG_1_8", false]], "beng_9_10 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.BENG_9_10", false]], "bin_bounds (moptipyapps.binpacking2d.packing_result.packingresult attribute)": [[4, "moptipyapps.binpacking2d.packing_result.PackingResult.bin_bounds", false]], "bin_bounds (moptipyapps.binpacking2d.packing_statistics.packingstatistics attribute)": [[4, "moptipyapps.binpacking2d.packing_statistics.PackingStatistics.bin_bounds", false]], "bin_count_and_empty() (in module moptipyapps.binpacking2d.objectives.bin_count_and_empty)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_empty.bin_count_and_empty", false]], "bin_count_and_last_empty() (in module moptipyapps.binpacking2d.objectives.bin_count_and_last_empty)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.bin_count_and_last_empty", false]], "bin_count_and_last_skyline() (in module moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.bin_count_and_last_skyline", false]], "bin_count_and_last_small() (in module moptipyapps.binpacking2d.objectives.bin_count_and_last_small)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_small.bin_count_and_last_small", false]], "bin_count_and_lowest_skyline() (in module moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.bin_count_and_lowest_skyline", false]], "bin_count_and_small() (in module moptipyapps.binpacking2d.objectives.bin_count_and_small)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_small.bin_count_and_small", false]], "bin_count_name (in module moptipyapps.binpacking2d.objectives.bin_count)": [[7, "moptipyapps.binpacking2d.objectives.bin_count.BIN_COUNT_NAME", false]], "bin_height (in module moptipyapps.binpacking2d.instance)": [[4, "moptipyapps.binpacking2d.instance.BIN_HEIGHT", false]], "bin_height (moptipyapps.binpacking2d.instance.instance attribute)": [[4, "moptipyapps.binpacking2d.instance.Instance.bin_height", false]], "bin_height (moptipyapps.binpacking2d.instgen.instance_space.instancespace attribute)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.bin_height", false]], "bin_height (moptipyapps.binpacking2d.packing_result.packingresult attribute)": [[4, "moptipyapps.binpacking2d.packing_result.PackingResult.bin_height", false]], "bin_height (moptipyapps.binpacking2d.packing_statistics.packingstatistics attribute)": [[4, "moptipyapps.binpacking2d.packing_statistics.PackingStatistics.bin_height", false]], "bin_width (in module moptipyapps.binpacking2d.instance)": [[4, "moptipyapps.binpacking2d.instance.BIN_WIDTH", false]], "bin_width (moptipyapps.binpacking2d.instance.instance attribute)": [[4, "moptipyapps.binpacking2d.instance.Instance.bin_width", false]], "bin_width (moptipyapps.binpacking2d.instgen.instance_space.instancespace attribute)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.bin_width", false]], "bin_width (moptipyapps.binpacking2d.packing_result.packingresult attribute)": [[4, "moptipyapps.binpacking2d.packing_result.PackingResult.bin_width", false]], "bin_width (moptipyapps.binpacking2d.packing_statistics.packingstatistics attribute)": [[4, "moptipyapps.binpacking2d.packing_statistics.PackingStatistics.bin_width", false]], "bincount (class in moptipyapps.binpacking2d.objectives.bin_count)": [[7, "moptipyapps.binpacking2d.objectives.bin_count.BinCount", false]], "bincountandempty (class in moptipyapps.binpacking2d.objectives.bin_count_and_empty)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_empty.BinCountAndEmpty", false]], "bincountandlastempty (class in moptipyapps.binpacking2d.objectives.bin_count_and_last_empty)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty", false]], "bincountandlastskyline (class in moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.BinCountAndLastSkyline", false]], "bincountandlastsmall (class in moptipyapps.binpacking2d.objectives.bin_count_and_last_small)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall", false]], "bincountandlowestskyline (class in moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.BinCountAndLowestSkyline", false]], "bincountandsmall (class in moptipyapps.binpacking2d.objectives.bin_count_and_small)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_small.BinCountAndSmall", false]], "binpacking_instances_for_tests() (in module moptipyapps.tests.on_binpacking2d)": [[14, "moptipyapps.tests.on_binpacking2d.binpacking_instances_for_tests", false]], "bks() (moptipyapps.qap.instance.instance method)": [[12, "moptipyapps.qap.instance.Instance.bks", false]], "brkga_bpp_2r (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.BRKGA_BPP_2R", false]], "brkga_bpp_anb (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.BRKGA_BPP_ANB", false]], "build() (moptipyapps.dynamic_control.controllers.codegen.codegenerator method)": [[9, "moptipyapps.dynamic_control.controllers.codegen.CodeGenerator.build", false]], "bye_penalty (moptipyapps.ttp.plan_length.gameplanlength attribute)": [[17, "moptipyapps.ttp.plan_length.GamePlanLength.bye_penalty", false]], "class_10_100 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_10_100", false]], "class_10_20 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_10_20", false]], "class_10_40 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_10_40", false]], "class_10_60 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_10_60", false]], "class_10_80 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_10_80", false]], "class_1_100 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_1_100", false]], "class_1_20 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_1_20", false]], "class_1_40 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_1_40", false]], "class_1_60 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_1_60", false]], "class_1_80 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_1_80", false]], "class_2_100 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_2_100", false]], "class_2_20 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_2_20", false]], "class_2_40 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_2_40", false]], "class_2_60 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_2_60", false]], "class_2_80 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_2_80", false]], "class_3_100 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_3_100", false]], "class_3_20 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_3_20", false]], "class_3_40 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_3_40", false]], "class_3_60 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_3_60", false]], "class_3_80 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_3_80", false]], "class_4_100 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_4_100", false]], "class_4_20 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_4_20", false]], "class_4_40 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_4_40", false]], "class_4_60 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_4_60", false]], "class_4_80 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_4_80", false]], "class_5_100 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_5_100", false]], "class_5_20 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_5_20", false]], "class_5_40 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_5_40", false]], "class_5_60 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_5_60", false]], "class_5_80 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_5_80", false]], "class_6_100 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_6_100", false]], "class_6_20 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_6_20", false]], "class_6_40 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_6_40", false]], "class_6_60 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_6_60", false]], "class_6_80 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_6_80", false]], "class_7_100 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_7_100", false]], "class_7_20 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_7_20", false]], "class_7_40 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_7_40", false]], "class_7_60 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_7_60", false]], "class_7_80 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_7_80", false]], "class_8_100 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_8_100", false]], "class_8_20 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_8_20", false]], "class_8_40 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_8_40", false]], "class_8_60 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_8_60", false]], "class_8_80 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_8_80", false]], "class_9_100 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_9_100", false]], "class_9_20 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_9_20", false]], "class_9_40 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_9_40", false]], "class_9_60 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_9_60", false]], "class_9_80 (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_9_80", false]], "class_and_beng (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.CLASS_AND_BENG", false]], "cmaes() (in module moptipyapps.binpacking2d.instgen.experiment)": [[6, "moptipyapps.binpacking2d.instgen.experiment.cmaes", false]], "cmaes() (in module moptipyapps.dynamic_control.experiment_raw)": [[8, "moptipyapps.dynamic_control.experiment_raw.cmaes", false]], "cmaes_raw() (in module moptipyapps.dynamic_control.experiment_surrogate)": [[8, "moptipyapps.dynamic_control.experiment_surrogate.cmaes_raw", false]], "cmaes_surrogate() (in module moptipyapps.dynamic_control.experiment_surrogate)": [[8, "moptipyapps.dynamic_control.experiment_surrogate.cmaes_surrogate", false]], "codegenerator (class in moptipyapps.dynamic_control.controllers.codegen)": [[9, "moptipyapps.dynamic_control.controllers.codegen.CodeGenerator", false]], "collector() (moptipyapps.dynamic_control.results_log.resultslog method)": [[8, "moptipyapps.dynamic_control.results_log.ResultsLog.collector", false]], "collector() (moptipyapps.dynamic_control.results_plot.resultsplot method)": [[8, "moptipyapps.dynamic_control.results_plot.ResultsPlot.collector", false]], "control_dims (moptipyapps.dynamic_control.controller.controller attribute)": [[8, "moptipyapps.dynamic_control.controller.Controller.control_dims", false]], "control_dims (moptipyapps.dynamic_control.system.system attribute)": [[8, "moptipyapps.dynamic_control.system.System.control_dims", false]], "controller (class in moptipyapps.dynamic_control.controller)": [[8, "moptipyapps.dynamic_control.controller.Controller", false]], "controller (moptipyapps.dynamic_control.instance.instance attribute)": [[8, "moptipyapps.dynamic_control.instance.Instance.controller", false]], "controller() (moptipyapps.dynamic_control.controller.controller method)": [[8, "moptipyapps.dynamic_control.controller.Controller.controller", false]], "copy() (moptipyapps.binpacking2d.instgen.instance_space.instancespace method)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.copy", false]], "copy() (moptipyapps.binpacking2d.packing_space.packingspace method)": [[4, "moptipyapps.binpacking2d.packing_space.PackingSpace.copy", false]], "count_errors() (in module moptipyapps.ttp.errors)": [[17, "moptipyapps.ttp.errors.count_errors", false]], "create() (moptipyapps.binpacking2d.instgen.instance_space.instancespace method)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.create", false]], "create() (moptipyapps.binpacking2d.packing_space.packingspace method)": [[4, "moptipyapps.binpacking2d.packing_space.PackingSpace.create", false]], "create() (moptipyapps.ttp.game_plan_space.gameplanspace method)": [[17, "moptipyapps.ttp.game_plan_space.GamePlanSpace.create", false]], "csvreader (class in moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.CsvReader", false]], "csvreader (class in moptipyapps.binpacking2d.packing_statistics)": [[4, "moptipyapps.binpacking2d.packing_statistics.CsvReader", false]], "csvwriter (class in moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.CsvWriter", false]], "csvwriter (class in moptipyapps.binpacking2d.packing_statistics)": [[4, "moptipyapps.binpacking2d.packing_statistics.CsvWriter", false]], "cubic() (in module moptipyapps.dynamic_control.controllers.cubic)": [[9, "moptipyapps.dynamic_control.controllers.cubic.cubic", false]], "decode() (moptipyapps.binpacking2d.encodings.ibl_encoding_1.improvedbottomleftencoding1 method)": [[5, "moptipyapps.binpacking2d.encodings.ibl_encoding_1.ImprovedBottomLeftEncoding1.decode", false]], "decode() (moptipyapps.binpacking2d.encodings.ibl_encoding_2.improvedbottomleftencoding2 method)": [[5, "moptipyapps.binpacking2d.encodings.ibl_encoding_2.ImprovedBottomLeftEncoding2.decode", false]], "decode() (moptipyapps.binpacking2d.instgen.inst_decoding.instancedecoder method)": [[6, "moptipyapps.binpacking2d.instgen.inst_decoding.InstanceDecoder.decode", false]], "default_executors (in module moptipyapps.binpacking2d.instgen.hardness)": [[6, "moptipyapps.binpacking2d.instgen.hardness.DEFAULT_EXECUTORS", false]], "default_objectives (in module moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.DEFAULT_OBJECTIVES", false]], "default_packing_item_str() (in module moptipyapps.binpacking2d.plot_packing)": [[4, "moptipyapps.binpacking2d.plot_packing.default_packing_item_str", false]], "describe_parameterization() (moptipyapps.dynamic_control.instance.instance method)": [[8, "moptipyapps.dynamic_control.instance.Instance.describe_parameterization", false]], "describe_system() (moptipyapps.dynamic_control.system.system method)": [[8, "moptipyapps.dynamic_control.system.System.describe_system", false]], "describe_system_without_control() (moptipyapps.dynamic_control.system.system method)": [[8, "moptipyapps.dynamic_control.system.System.describe_system_without_control", false]], "diff_from_ode() (in module moptipyapps.dynamic_control.ode)": [[8, "moptipyapps.dynamic_control.ode.diff_from_ode", false]], "distances (moptipyapps.qap.instance.instance attribute)": [[12, "moptipyapps.qap.instance.Instance.distances", false]], "do_log_h (moptipyapps.tsp.fea1p1_revn.tspfea1p1revn attribute)": [[15, "moptipyapps.tsp.fea1p1_revn.TSPFEA1p1revn.do_log_h", false]], "download_2dpacklib_instances() (in module moptipyapps.binpacking2d.make_instances)": [[4, "moptipyapps.binpacking2d.make_instances.download_2dpacklib_instances", false]], "ealgfi (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.EALGFI", false]], "element (class in moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.Element", false]], "encoding (moptipyapps.binpacking2d.instgen.problem.problem attribute)": [[6, "moptipyapps.binpacking2d.instgen.problem.Problem.encoding", false]], "end() (moptipyapps.dynamic_control.model_objective.modelobjective method)": [[8, "moptipyapps.dynamic_control.model_objective.ModelObjective.end", false]], "end_color (in module moptipyapps.dynamic_control.results_plot)": [[8, "moptipyapps.dynamic_control.results_plot.END_COLOR", false]], "end_marker (in module moptipyapps.dynamic_control.results_plot)": [[8, "moptipyapps.dynamic_control.results_plot.END_MARKER", false]], "end_result (moptipyapps.binpacking2d.packing_result.packingresult attribute)": [[4, "moptipyapps.binpacking2d.packing_result.PackingResult.end_result", false]], "end_statistics (moptipyapps.binpacking2d.packing_statistics.packingstatistics attribute)": [[4, "moptipyapps.binpacking2d.packing_statistics.PackingStatistics.end_statistics", false]], "endline() (moptipyapps.dynamic_control.controllers.codegen.codegenerator method)": [[9, "moptipyapps.dynamic_control.controllers.codegen.CodeGenerator.endline", false]], "equations() (moptipyapps.dynamic_control.system.system method)": [[8, "moptipyapps.dynamic_control.system.System.equations", false]], "errors (class in moptipyapps.binpacking2d.instgen.errors)": [[6, "moptipyapps.binpacking2d.instgen.errors.Errors", false]], "errors (class in moptipyapps.ttp.errors)": [[17, "moptipyapps.ttp.errors.Errors", false]], "errors (moptipyapps.binpacking2d.instgen.errors_and_hardness.errorsandhardness attribute)": [[6, "moptipyapps.binpacking2d.instgen.errors_and_hardness.ErrorsAndHardness.errors", false]], "errorsandhardness (class in moptipyapps.binpacking2d.instgen.errors_and_hardness)": [[6, "moptipyapps.binpacking2d.instgen.errors_and_hardness.ErrorsAndHardness", false]], "evaluate() (moptipyapps.binpacking2d.instgen.errors.errors method)": [[6, "moptipyapps.binpacking2d.instgen.errors.Errors.evaluate", false]], "evaluate() (moptipyapps.binpacking2d.instgen.errors_and_hardness.errorsandhardness method)": [[6, "moptipyapps.binpacking2d.instgen.errors_and_hardness.ErrorsAndHardness.evaluate", false]], "evaluate() (moptipyapps.binpacking2d.instgen.hardness.hardness method)": [[6, "moptipyapps.binpacking2d.instgen.hardness.Hardness.evaluate", false]], "evaluate() (moptipyapps.binpacking2d.objectives.bin_count.bincount method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count.BinCount.evaluate", false]], "evaluate() (moptipyapps.binpacking2d.objectives.bin_count_and_empty.bincountandempty method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_empty.BinCountAndEmpty.evaluate", false]], "evaluate() (moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.bincountandlastempty method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty.evaluate", false]], "evaluate() (moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.bincountandlastskyline method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.BinCountAndLastSkyline.evaluate", false]], "evaluate() (moptipyapps.binpacking2d.objectives.bin_count_and_last_small.bincountandlastsmall method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall.evaluate", false]], "evaluate() (moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.bincountandlowestskyline method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.BinCountAndLowestSkyline.evaluate", false]], "evaluate() (moptipyapps.binpacking2d.objectives.bin_count_and_small.bincountandsmall method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_small.BinCountAndSmall.evaluate", false]], "evaluate() (moptipyapps.dynamic_control.model_objective.modelobjective method)": [[8, "moptipyapps.dynamic_control.model_objective.ModelObjective.evaluate", false]], "evaluate() (moptipyapps.dynamic_control.objective.figureofmerit method)": [[8, "moptipyapps.dynamic_control.objective.FigureOfMerit.evaluate", false]], "evaluate() (moptipyapps.qap.objective.qapobjective method)": [[12, "moptipyapps.qap.objective.QAPObjective.evaluate", false]], "evaluate() (moptipyapps.tsp.tour_length.tourlength method)": [[15, "moptipyapps.tsp.tour_length.TourLength.evaluate", false]], "evaluate() (moptipyapps.ttp.errors.errors method)": [[17, "moptipyapps.ttp.errors.Errors.evaluate", false]], "evaluate() (moptipyapps.ttp.plan_length.gameplanlength method)": [[17, "moptipyapps.ttp.plan_length.GamePlanLength.evaluate", false]], "executors (moptipyapps.binpacking2d.instgen.hardness.hardness attribute)": [[6, "moptipyapps.binpacking2d.instgen.hardness.Hardness.executors", false]], "fancy_logs (moptipyapps.dynamic_control.surrogate_optimizer.surrogateoptimizer attribute)": [[8, "moptipyapps.dynamic_control.surrogate_optimizer.SurrogateOptimizer.fancy_logs", false]], "fea() (in module moptipyapps.binpacking2d.experiment)": [[4, "moptipyapps.binpacking2d.experiment.fea", false]], "fes_for_training (moptipyapps.dynamic_control.surrogate_optimizer.surrogateoptimizer attribute)": [[8, "moptipyapps.dynamic_control.surrogate_optimizer.SurrogateOptimizer.fes_for_training", false]], "fes_for_warmup (moptipyapps.dynamic_control.surrogate_optimizer.surrogateoptimizer attribute)": [[8, "moptipyapps.dynamic_control.surrogate_optimizer.SurrogateOptimizer.fes_for_warmup", false]], "fes_per_model_run (moptipyapps.dynamic_control.surrogate_optimizer.surrogateoptimizer attribute)": [[8, "moptipyapps.dynamic_control.surrogate_optimizer.SurrogateOptimizer.fes_per_model_run", false]], "figureofmerit (class in moptipyapps.dynamic_control.objective)": [[8, "moptipyapps.dynamic_control.objective.FigureOfMerit", false]], "figureofmeritle (class in moptipyapps.dynamic_control.objective)": [[8, "moptipyapps.dynamic_control.objective.FigureOfMeritLE", false]], "flow_power (moptipyapps.order1d.instance.instance attribute)": [[11, "moptipyapps.order1d.instance.Instance.flow_power", false]], "flows (moptipyapps.qap.instance.instance attribute)": [[12, "moptipyapps.qap.instance.Instance.flows", false]], "from_2dpacklib() (moptipyapps.binpacking2d.instance.instance static method)": [[4, "moptipyapps.binpacking2d.instance.Instance.from_2dpacklib", false]], "from_compact_str() (moptipyapps.binpacking2d.instance.instance static method)": [[4, "moptipyapps.binpacking2d.instance.Instance.from_compact_str", false]], "from_csv() (in module moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.from_csv", false]], "from_csv() (in module moptipyapps.binpacking2d.packing_statistics)": [[4, "moptipyapps.binpacking2d.packing_statistics.from_csv", false]], "from_file() (moptipyapps.tsp.instance.instance static method)": [[15, "moptipyapps.tsp.instance.Instance.from_file", false]], "from_file() (moptipyapps.ttp.instance.instance static method)": [[17, "moptipyapps.ttp.instance.Instance.from_file", false]], "from_log() (moptipyapps.binpacking2d.packing.packing static method)": [[4, "moptipyapps.binpacking2d.packing.Packing.from_log", false]], "from_logs() (in module moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.from_logs", false]], "from_packing_and_end_result() (in module moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.from_packing_and_end_result", false]], "from_packing_results() (in module moptipyapps.binpacking2d.packing_statistics)": [[4, "moptipyapps.binpacking2d.packing_statistics.from_packing_results", false]], "from_qaplib_stream() (moptipyapps.qap.instance.instance static method)": [[12, "moptipyapps.qap.instance.Instance.from_qaplib_stream", false]], "from_resource() (moptipyapps.binpacking2d.instance.instance static method)": [[4, "moptipyapps.binpacking2d.instance.Instance.from_resource", false]], "from_resource() (moptipyapps.qap.instance.instance static method)": [[12, "moptipyapps.qap.instance.Instance.from_resource", false]], "from_resource() (moptipyapps.tsp.instance.instance static method)": [[15, "moptipyapps.tsp.instance.Instance.from_resource", false]], "from_resource() (moptipyapps.ttp.instance.instance static method)": [[17, "moptipyapps.ttp.instance.Instance.from_resource", false]], "from_sequence_and_distance() (moptipyapps.order1d.instance.instance static method)": [[11, "moptipyapps.order1d.instance.Instance.from_sequence_and_distance", false]], "from_single_log() (in module moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.from_single_log", false]], "from_str() (moptipyapps.binpacking2d.instgen.instance_space.instancespace method)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.from_str", false]], "from_str() (moptipyapps.binpacking2d.packing_space.packingspace method)": [[4, "moptipyapps.binpacking2d.packing_space.PackingSpace.from_str", false]], "from_str() (moptipyapps.order1d.space.orderingspace method)": [[11, "moptipyapps.order1d.space.OrderingSpace.from_str", false]], "from_str() (moptipyapps.ttp.game_plan_space.gameplanspace method)": [[17, "moptipyapps.ttp.game_plan_space.GamePlanSpace.from_str", false]], "game_plan_dtype (moptipyapps.ttp.instance.instance attribute)": [[17, "moptipyapps.ttp.instance.Instance.game_plan_dtype", false]], "game_plan_length() (in module moptipyapps.ttp.plan_length)": [[17, "moptipyapps.ttp.plan_length.game_plan_length", false]], "gameencoding (class in moptipyapps.ttp.game_encoding)": [[17, "moptipyapps.ttp.game_encoding.GameEncoding", false]], "gameplan (class in moptipyapps.ttp.game_plan)": [[17, "moptipyapps.ttp.game_plan.GamePlan", false]], "gameplanlength (class in moptipyapps.ttp.plan_length)": [[17, "moptipyapps.ttp.plan_length.GamePlanLength", false]], "gameplanspace (class in moptipyapps.ttp.game_plan_space)": [[17, "moptipyapps.ttp.game_plan_space.GamePlanSpace", false]], "gamma (moptipyapps.dynamic_control.system.system attribute)": [[8, "moptipyapps.dynamic_control.system.System.gamma", false]], "get_bibtex() (moptipyapps.binpacking2d.bks.element method)": [[4, "moptipyapps.binpacking2d.bks.Element.get_bibtex", false]], "get_column_titles() (moptipyapps.binpacking2d.packing_result.csvwriter method)": [[4, "moptipyapps.binpacking2d.packing_result.CsvWriter.get_column_titles", false]], "get_column_titles() (moptipyapps.binpacking2d.packing_statistics.csvwriter method)": [[4, "moptipyapps.binpacking2d.packing_statistics.CsvWriter.get_column_titles", false]], "get_differentials() (moptipyapps.dynamic_control.objective.figureofmerit method)": [[8, "moptipyapps.dynamic_control.objective.FigureOfMerit.get_differentials", false]], "get_footer_bottom_comments() (moptipyapps.binpacking2d.packing_result.csvwriter method)": [[4, "moptipyapps.binpacking2d.packing_result.CsvWriter.get_footer_bottom_comments", false]], "get_footer_bottom_comments() (moptipyapps.binpacking2d.packing_statistics.csvwriter method)": [[4, "moptipyapps.binpacking2d.packing_statistics.CsvWriter.get_footer_bottom_comments", false]], "get_footer_comments() (moptipyapps.binpacking2d.packing_result.csvwriter method)": [[4, "moptipyapps.binpacking2d.packing_result.CsvWriter.get_footer_comments", false]], "get_footer_comments() (moptipyapps.binpacking2d.packing_statistics.csvwriter method)": [[4, "moptipyapps.binpacking2d.packing_statistics.CsvWriter.get_footer_comments", false]], "get_header_comments() (moptipyapps.binpacking2d.packing_result.csvwriter method)": [[4, "moptipyapps.binpacking2d.packing_result.CsvWriter.get_header_comments", false]], "get_header_comments() (moptipyapps.binpacking2d.packing_statistics.csvwriter method)": [[4, "moptipyapps.binpacking2d.packing_statistics.CsvWriter.get_header_comments", false]], "get_optimal_plan_length_bounds() (moptipyapps.ttp.instance.instance method)": [[17, "moptipyapps.ttp.instance.Instance.get_optimal_plan_length_bounds", false]], "get_related_work() (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.get_related_work", false]], "get_row() (moptipyapps.binpacking2d.packing_result.csvwriter method)": [[4, "moptipyapps.binpacking2d.packing_result.CsvWriter.get_row", false]], "get_row() (moptipyapps.binpacking2d.packing_statistics.csvwriter method)": [[4, "moptipyapps.binpacking2d.packing_statistics.CsvWriter.get_row", false]], "get_standard_item_sequence() (moptipyapps.binpacking2d.instance.instance method)": [[4, "moptipyapps.binpacking2d.instance.Instance.get_standard_item_sequence", false]], "get_x_dim() (moptipyapps.binpacking2d.instgen.inst_decoding.instancedecoder method)": [[6, "moptipyapps.binpacking2d.instgen.inst_decoding.InstanceDecoder.get_x_dim", false]], "grasp_vnd (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.GRASP_VND", false]], "groups_to_instances (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.GROUPS_TO_INSTANCES", false]], "hardness (class in moptipyapps.binpacking2d.instgen.hardness)": [[6, "moptipyapps.binpacking2d.instgen.hardness.Hardness", false]], "hardness (moptipyapps.binpacking2d.instgen.errors_and_hardness.errorsandhardness attribute)": [[6, "moptipyapps.binpacking2d.instgen.errors_and_hardness.ErrorsAndHardness.hardness", false]], "hhano_r (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.HHANO_R", false]], "hhano_sr (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.HHANO_SR", false]], "home_streak_max (moptipyapps.ttp.instance.instance attribute)": [[17, "moptipyapps.ttp.instance.Instance.home_streak_max", false]], "home_streak_min (moptipyapps.ttp.instance.instance attribute)": [[17, "moptipyapps.ttp.instance.Instance.home_streak_min", false]], "horizon (moptipyapps.order1d.instance.instance attribute)": [[11, "moptipyapps.order1d.instance.Instance.horizon", false]], "idx_bin (in module moptipyapps.binpacking2d.packing)": [[4, "moptipyapps.binpacking2d.packing.IDX_BIN", false]], "idx_bottom_y (in module moptipyapps.binpacking2d.packing)": [[4, "moptipyapps.binpacking2d.packing.IDX_BOTTOM_Y", false]], "idx_height (in module moptipyapps.binpacking2d.instance)": [[4, "moptipyapps.binpacking2d.instance.IDX_HEIGHT", false]], "idx_id (in module moptipyapps.binpacking2d.packing)": [[4, "moptipyapps.binpacking2d.packing.IDX_ID", false]], "idx_left_x (in module moptipyapps.binpacking2d.packing)": [[4, "moptipyapps.binpacking2d.packing.IDX_LEFT_X", false]], "idx_repetition (in module moptipyapps.binpacking2d.instance)": [[4, "moptipyapps.binpacking2d.instance.IDX_REPETITION", false]], "idx_right_x (in module moptipyapps.binpacking2d.packing)": [[4, "moptipyapps.binpacking2d.packing.IDX_RIGHT_X", false]], "idx_top_y (in module moptipyapps.binpacking2d.packing)": [[4, "moptipyapps.binpacking2d.packing.IDX_TOP_Y", false]], "idx_width (in module moptipyapps.binpacking2d.instance)": [[4, "moptipyapps.binpacking2d.instance.IDX_WIDTH", false]], "improvedbottomleftencoding1 (class in moptipyapps.binpacking2d.encodings.ibl_encoding_1)": [[5, "moptipyapps.binpacking2d.encodings.ibl_encoding_1.ImprovedBottomLeftEncoding1", false]], "improvedbottomleftencoding2 (class in moptipyapps.binpacking2d.encodings.ibl_encoding_2)": [[5, "moptipyapps.binpacking2d.encodings.ibl_encoding_2.ImprovedBottomLeftEncoding2", false]], "indent() (moptipyapps.dynamic_control.controllers.codegen.codegenerator method)": [[9, "moptipyapps.dynamic_control.controllers.codegen.CodeGenerator.indent", false]], "initialize() (moptipyapps.dynamic_control.objective.figureofmerit method)": [[8, "moptipyapps.dynamic_control.objective.FigureOfMerit.initialize", false]], "initialize() (moptipyapps.dynamic_control.surrogate_optimizer.surrogateoptimizer method)": [[8, "moptipyapps.dynamic_control.surrogate_optimizer.SurrogateOptimizer.initialize", false]], "inner_max_fes (in module moptipyapps.binpacking2d.instgen.experiment)": [[6, "moptipyapps.binpacking2d.instgen.experiment.INNER_MAX_FES", false]], "inner_runs (in module moptipyapps.binpacking2d.instgen.experiment)": [[6, "moptipyapps.binpacking2d.instgen.experiment.INNER_RUNS", false]], "inst_group_sort_key() (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.INST_GROUP_SORT_KEY", false]], "inst_name (moptipyapps.binpacking2d.instgen.instance_space.instancespace attribute)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.inst_name", false]], "instance (class in moptipyapps.binpacking2d.instance)": [[4, "moptipyapps.binpacking2d.instance.Instance", false]], "instance (class in moptipyapps.dynamic_control.instance)": [[8, "moptipyapps.dynamic_control.instance.Instance", false]], "instance (class in moptipyapps.order1d.instance)": [[11, "moptipyapps.order1d.instance.Instance", false]], "instance (class in moptipyapps.qap.instance)": [[12, "moptipyapps.qap.instance.Instance", false]], "instance (class in moptipyapps.tsp.instance)": [[15, "moptipyapps.tsp.instance.Instance", false]], "instance (class in moptipyapps.ttp.instance)": [[17, "moptipyapps.ttp.instance.Instance", false]], "instance (moptipyapps.binpacking2d.packing.packing attribute)": [[4, "moptipyapps.binpacking2d.packing.Packing.instance", false]], "instance (moptipyapps.binpacking2d.packing_space.packingspace attribute)": [[4, "moptipyapps.binpacking2d.packing_space.PackingSpace.instance", false]], "instance (moptipyapps.dynamic_control.objective.figureofmerit attribute)": [[8, "moptipyapps.dynamic_control.objective.FigureOfMerit.instance", false]], "instance (moptipyapps.order1d.space.orderingspace attribute)": [[11, "moptipyapps.order1d.space.OrderingSpace.instance", false]], "instance (moptipyapps.qap.objective.qapobjective attribute)": [[12, "moptipyapps.qap.objective.QAPObjective.instance", false]], "instance (moptipyapps.tsp.fea1p1_revn.tspfea1p1revn attribute)": [[15, "moptipyapps.tsp.fea1p1_revn.TSPFEA1p1revn.instance", false]], "instance (moptipyapps.tsp.tour_length.tourlength attribute)": [[15, "moptipyapps.tsp.tour_length.TourLength.instance", false]], "instance (moptipyapps.ttp.errors.errors attribute)": [[17, "moptipyapps.ttp.errors.Errors.instance", false]], "instance (moptipyapps.ttp.game_encoding.gameencoding attribute)": [[17, "moptipyapps.ttp.game_encoding.GameEncoding.instance", false]], "instance (moptipyapps.ttp.game_plan.gameplan attribute)": [[17, "moptipyapps.ttp.game_plan.GamePlan.instance", false]], "instance (moptipyapps.ttp.game_plan_space.gameplanspace attribute)": [[17, "moptipyapps.ttp.game_plan_space.GamePlanSpace.instance", false]], "instance (moptipyapps.ttp.plan_length.gameplanlength attribute)": [[17, "moptipyapps.ttp.plan_length.GamePlanLength.instance", false]], "instancedecoder (class in moptipyapps.binpacking2d.instgen.inst_decoding)": [[6, "moptipyapps.binpacking2d.instgen.inst_decoding.InstanceDecoder", false]], "instances_resource (in module moptipyapps.binpacking2d.instance)": [[4, "moptipyapps.binpacking2d.instance.INSTANCES_RESOURCE", false]], "instances_to_groups (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.INSTANCES_TO_GROUPS", false]], "instancespace (class in moptipyapps.binpacking2d.instgen.instance_space)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace", false]], "interesting_point_objective() (in module moptipyapps.dynamic_control.starting_points)": [[8, "moptipyapps.dynamic_control.starting_points.interesting_point_objective", false]], "interesting_point_transform() (in module moptipyapps.dynamic_control.starting_points)": [[8, "moptipyapps.dynamic_control.starting_points.interesting_point_transform", false]], "internal_sep (in module moptipyapps.binpacking2d.instance)": [[4, "moptipyapps.binpacking2d.instance.INTERNAL_SEP", false]], "is_always_integer() (moptipyapps.binpacking2d.instgen.errors.errors method)": [[6, "moptipyapps.binpacking2d.instgen.errors.Errors.is_always_integer", false]], "is_always_integer() (moptipyapps.binpacking2d.instgen.errors_and_hardness.errorsandhardness method)": [[6, "moptipyapps.binpacking2d.instgen.errors_and_hardness.ErrorsAndHardness.is_always_integer", false]], "is_always_integer() (moptipyapps.binpacking2d.instgen.hardness.hardness method)": [[6, "moptipyapps.binpacking2d.instgen.hardness.Hardness.is_always_integer", false]], "is_always_integer() (moptipyapps.binpacking2d.objectives.bin_count.bincount method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count.BinCount.is_always_integer", false]], "is_always_integer() (moptipyapps.ttp.errors.errors method)": [[17, "moptipyapps.ttp.errors.Errors.is_always_integer", false]], "is_always_integer() (moptipyapps.ttp.plan_length.gameplanlength method)": [[17, "moptipyapps.ttp.plan_length.GamePlanLength.is_always_integer", false]], "is_equal() (moptipyapps.binpacking2d.instgen.instance_space.instancespace method)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.is_equal", false]], "is_equal() (moptipyapps.binpacking2d.packing_space.packingspace method)": [[4, "moptipyapps.binpacking2d.packing_space.PackingSpace.is_equal", false]], "is_equal() (moptipyapps.ttp.game_plan_space.gameplanspace method)": [[17, "moptipyapps.ttp.game_plan_space.GamePlanSpace.is_equal", false]], "is_symmetric (moptipyapps.tsp.instance.instance attribute)": [[15, "moptipyapps.tsp.instance.Instance.is_symmetric", false]], "item_height_max (moptipyapps.binpacking2d.instgen.instance_space.instancespace attribute)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.item_height_max", false]], "item_height_min (moptipyapps.binpacking2d.instgen.instance_space.instancespace attribute)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.item_height_min", false]], "item_width_max (moptipyapps.binpacking2d.instgen.instance_space.instancespace attribute)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.item_width_max", false]], "item_width_min (moptipyapps.binpacking2d.instgen.instance_space.instancespace attribute)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.item_width_min", false]], "j_from_ode() (in module moptipyapps.dynamic_control.ode)": [[8, "moptipyapps.dynamic_control.ode.j_from_ode", false]], "join_instances_to_compact() (in module moptipyapps.binpacking2d.make_instances)": [[4, "moptipyapps.binpacking2d.make_instances.join_instances_to_compact", false]], "key_bin_height (in module moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.KEY_BIN_HEIGHT", false]], "key_bin_width (in module moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.KEY_BIN_WIDTH", false]], "key_n_different_items (in module moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.KEY_N_DIFFERENT_ITEMS", false]], "key_n_items (in module moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.KEY_N_ITEMS", false]], "linear() (in module moptipyapps.dynamic_control.controllers.linear)": [[9, "moptipyapps.dynamic_control.controllers.linear.linear", false]], "list_resource_tours() (in module moptipyapps.tsp.known_optima)": [[15, "moptipyapps.tsp.known_optima.list_resource_tours", false]], "list_resources() (moptipyapps.binpacking2d.instance.instance static method)": [[4, "moptipyapps.binpacking2d.instance.Instance.list_resources", false]], "list_resources() (moptipyapps.qap.instance.instance static method)": [[12, "moptipyapps.qap.instance.Instance.list_resources", false]], "list_resources() (moptipyapps.tsp.instance.instance static method)": [[15, "moptipyapps.tsp.instance.Instance.list_resources", false]], "list_resources() (moptipyapps.ttp.instance.instance static method)": [[17, "moptipyapps.ttp.instance.Instance.list_resources", false]], "list_resources_groups() (moptipyapps.binpacking2d.instance.instance static method)": [[4, "moptipyapps.binpacking2d.instance.Instance.list_resources_groups", false]], "log_parameters_to() (moptipyapps.binpacking2d.encodings.ibl_encoding_1.improvedbottomleftencoding1 method)": [[5, "moptipyapps.binpacking2d.encodings.ibl_encoding_1.ImprovedBottomLeftEncoding1.log_parameters_to", false]], "log_parameters_to() (moptipyapps.binpacking2d.encodings.ibl_encoding_2.improvedbottomleftencoding2 method)": [[5, "moptipyapps.binpacking2d.encodings.ibl_encoding_2.ImprovedBottomLeftEncoding2.log_parameters_to", false]], "log_parameters_to() (moptipyapps.binpacking2d.instance.instance method)": [[4, "moptipyapps.binpacking2d.instance.Instance.log_parameters_to", false]], "log_parameters_to() (moptipyapps.binpacking2d.instgen.errors.errors method)": [[6, "moptipyapps.binpacking2d.instgen.errors.Errors.log_parameters_to", false]], "log_parameters_to() (moptipyapps.binpacking2d.instgen.errors_and_hardness.errorsandhardness method)": [[6, "moptipyapps.binpacking2d.instgen.errors_and_hardness.ErrorsAndHardness.log_parameters_to", false]], "log_parameters_to() (moptipyapps.binpacking2d.instgen.hardness.hardness method)": [[6, "moptipyapps.binpacking2d.instgen.hardness.Hardness.log_parameters_to", false]], "log_parameters_to() (moptipyapps.binpacking2d.instgen.instance_space.instancespace method)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.log_parameters_to", false]], "log_parameters_to() (moptipyapps.binpacking2d.packing_space.packingspace method)": [[4, "moptipyapps.binpacking2d.packing_space.PackingSpace.log_parameters_to", false]], "log_parameters_to() (moptipyapps.dynamic_control.controller.controller method)": [[8, "moptipyapps.dynamic_control.controller.Controller.log_parameters_to", false]], "log_parameters_to() (moptipyapps.dynamic_control.instance.instance method)": [[8, "moptipyapps.dynamic_control.instance.Instance.log_parameters_to", false]], "log_parameters_to() (moptipyapps.dynamic_control.model_objective.modelobjective method)": [[8, "moptipyapps.dynamic_control.model_objective.ModelObjective.log_parameters_to", false]], "log_parameters_to() (moptipyapps.dynamic_control.objective.figureofmerit method)": [[8, "moptipyapps.dynamic_control.objective.FigureOfMerit.log_parameters_to", false]], "log_parameters_to() (moptipyapps.dynamic_control.surrogate_optimizer.surrogateoptimizer method)": [[8, "moptipyapps.dynamic_control.surrogate_optimizer.SurrogateOptimizer.log_parameters_to", false]], "log_parameters_to() (moptipyapps.dynamic_control.system.system method)": [[8, "moptipyapps.dynamic_control.system.System.log_parameters_to", false]], "log_parameters_to() (moptipyapps.dynamic_control.system_model.systemmodel method)": [[8, "moptipyapps.dynamic_control.system_model.SystemModel.log_parameters_to", false]], "log_parameters_to() (moptipyapps.order1d.instance.instance method)": [[11, "moptipyapps.order1d.instance.Instance.log_parameters_to", false]], "log_parameters_to() (moptipyapps.qap.instance.instance method)": [[12, "moptipyapps.qap.instance.Instance.log_parameters_to", false]], "log_parameters_to() (moptipyapps.qap.objective.qapobjective method)": [[12, "moptipyapps.qap.objective.QAPObjective.log_parameters_to", false]], "log_parameters_to() (moptipyapps.tsp.ea1p1_revn.tspea1p1revn method)": [[15, "moptipyapps.tsp.ea1p1_revn.TSPEA1p1revn.log_parameters_to", false]], "log_parameters_to() (moptipyapps.tsp.fea1p1_revn.tspfea1p1revn method)": [[15, "moptipyapps.tsp.fea1p1_revn.TSPFEA1p1revn.log_parameters_to", false]], "log_parameters_to() (moptipyapps.tsp.instance.instance method)": [[15, "moptipyapps.tsp.instance.Instance.log_parameters_to", false]], "log_parameters_to() (moptipyapps.tsp.tour_length.tourlength method)": [[15, "moptipyapps.tsp.tour_length.TourLength.log_parameters_to", false]], "log_parameters_to() (moptipyapps.ttp.game_plan_space.gameplanspace method)": [[17, "moptipyapps.ttp.game_plan_space.GamePlanSpace.log_parameters_to", false]], "log_parameters_to() (moptipyapps.ttp.instance.instance method)": [[17, "moptipyapps.ttp.instance.Instance.log_parameters_to", false]], "log_parameters_to() (moptipyapps.ttp.plan_length.gameplanlength method)": [[17, "moptipyapps.ttp.plan_length.GamePlanLength.log_parameters_to", false]], "lorenz_111 (in module moptipyapps.dynamic_control.systems.lorenz)": [[10, "moptipyapps.dynamic_control.systems.lorenz.LORENZ_111", false]], "lorenz_4 (in module moptipyapps.dynamic_control.systems.lorenz)": [[10, "moptipyapps.dynamic_control.systems.lorenz.LORENZ_4", false]], "lower_bound (moptipyapps.qap.instance.instance attribute)": [[12, "moptipyapps.qap.instance.Instance.lower_bound", false]], "lower_bound() (moptipyapps.binpacking2d.instgen.errors.errors method)": [[6, "moptipyapps.binpacking2d.instgen.errors.Errors.lower_bound", false]], "lower_bound() (moptipyapps.binpacking2d.instgen.errors_and_hardness.errorsandhardness method)": [[6, "moptipyapps.binpacking2d.instgen.errors_and_hardness.ErrorsAndHardness.lower_bound", false]], "lower_bound() (moptipyapps.binpacking2d.instgen.hardness.hardness method)": [[6, "moptipyapps.binpacking2d.instgen.hardness.Hardness.lower_bound", false]], "lower_bound() (moptipyapps.binpacking2d.objectives.bin_count.bincount method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count.BinCount.lower_bound", false]], "lower_bound() (moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.bincountandlastempty method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty.lower_bound", false]], "lower_bound() (moptipyapps.binpacking2d.objectives.bin_count_and_last_small.bincountandlastsmall method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall.lower_bound", false]], "lower_bound() (moptipyapps.dynamic_control.model_objective.modelobjective method)": [[8, "moptipyapps.dynamic_control.model_objective.ModelObjective.lower_bound", false]], "lower_bound() (moptipyapps.dynamic_control.objective.figureofmerit method)": [[8, "moptipyapps.dynamic_control.objective.FigureOfMerit.lower_bound", false]], "lower_bound() (moptipyapps.qap.objective.qapobjective method)": [[12, "moptipyapps.qap.objective.QAPObjective.lower_bound", false]], "lower_bound() (moptipyapps.tsp.tour_length.tourlength method)": [[15, "moptipyapps.tsp.tour_length.TourLength.lower_bound", false]], "lower_bound() (moptipyapps.ttp.errors.errors method)": [[17, "moptipyapps.ttp.errors.Errors.lower_bound", false]], "lower_bound() (moptipyapps.ttp.plan_length.gameplanlength method)": [[17, "moptipyapps.ttp.plan_length.GamePlanLength.lower_bound", false]], "lower_bound_bins (moptipyapps.binpacking2d.instance.instance attribute)": [[4, "moptipyapps.binpacking2d.instance.Instance.lower_bound_bins", false]], "lower_bounds_bin_count (in module moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.LOWER_BOUNDS_BIN_COUNT", false]], "make_2dpacklib_resource() (in module moptipyapps.binpacking2d.make_instances)": [[4, "moptipyapps.binpacking2d.make_instances.make_2dpacklib_resource", false]], "make_3_couple_oscillators() (in module moptipyapps.dynamic_control.systems.three_coupled_oscillators)": [[10, "moptipyapps.dynamic_control.systems.three_coupled_oscillators.make_3_couple_oscillators", false]], "make_ann() (in module moptipyapps.dynamic_control.controllers.ann)": [[9, "moptipyapps.dynamic_control.controllers.ann.make_ann", false]], "make_comparison_table() (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.make_comparison_table", false]], "make_comparison_table_data() (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.make_comparison_table_data", false]], "make_instances() (in module moptipyapps.dynamic_control.experiment_raw)": [[8, "moptipyapps.dynamic_control.experiment_raw.make_instances", false]], "make_instances() (in module moptipyapps.dynamic_control.experiment_surrogate)": [[8, "moptipyapps.dynamic_control.experiment_surrogate.make_instances", false]], "make_interesting_starting_points() (in module moptipyapps.dynamic_control.starting_points)": [[8, "moptipyapps.dynamic_control.starting_points.make_interesting_starting_points", false]], "make_lorenz() (in module moptipyapps.dynamic_control.systems.lorenz)": [[10, "moptipyapps.dynamic_control.systems.lorenz.make_lorenz", false]], "make_packing_invalid() (in module moptipyapps.tests.on_binpacking2d)": [[14, "moptipyapps.tests.on_binpacking2d.make_packing_invalid", false]], "make_packing_valid() (in module moptipyapps.tests.on_binpacking2d)": [[14, "moptipyapps.tests.on_binpacking2d.make_packing_valid", false]], "make_stuart_landau() (in module moptipyapps.dynamic_control.systems.stuart_landau)": [[10, "moptipyapps.dynamic_control.systems.stuart_landau.make_stuart_landau", false]], "make_tour_invalid() (in module moptipyapps.tests.on_tsp)": [[14, "moptipyapps.tests.on_tsp.make_tour_invalid", false]], "make_tour_valid() (in module moptipyapps.tests.on_tsp)": [[14, "moptipyapps.tests.on_tsp.make_tour_valid", false]], "map_games() (in module moptipyapps.ttp.game_encoding)": [[17, "moptipyapps.ttp.game_encoding.map_games", false]], "max_fes (in module moptipyapps.binpacking2d.experiment)": [[4, "moptipyapps.binpacking2d.experiment.MAX_FES", false]], "max_fes (in module moptipyapps.binpacking2d.instgen.experiment)": [[6, "moptipyapps.binpacking2d.instgen.experiment.MAX_FES", false]], "max_fes (in module moptipyapps.dynamic_control.experiment_surrogate)": [[8, "moptipyapps.dynamic_control.experiment_surrogate.MAX_FES", false]], "max_fes (moptipyapps.binpacking2d.instgen.hardness.hardness attribute)": [[6, "moptipyapps.binpacking2d.instgen.hardness.Hardness.max_fes", false]], "min_anns() (in module moptipyapps.dynamic_control.controllers.min_ann)": [[9, "moptipyapps.dynamic_control.controllers.min_ann.min_anns", false]], "min_bins (moptipyapps.binpacking2d.instgen.instance_space.instancespace attribute)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.min_bins", false]], "model (moptipyapps.dynamic_control.system_model.systemmodel attribute)": [[8, "moptipyapps.dynamic_control.system_model.SystemModel.model", false]], "modelobjective (class in moptipyapps.dynamic_control.model_objective)": [[8, "moptipyapps.dynamic_control.model_objective.ModelObjective", false]], "module": [[3, "module-moptipyapps", false], [3, "module-moptipyapps.shared", false], [3, "module-moptipyapps.version", false], [4, "module-moptipyapps.binpacking2d", false], [4, "module-moptipyapps.binpacking2d.bks", false], [4, "module-moptipyapps.binpacking2d.experiment", false], [4, "module-moptipyapps.binpacking2d.instance", false], [4, "module-moptipyapps.binpacking2d.make_instances", false], [4, "module-moptipyapps.binpacking2d.packing", false], [4, "module-moptipyapps.binpacking2d.packing_result", false], [4, "module-moptipyapps.binpacking2d.packing_space", false], [4, "module-moptipyapps.binpacking2d.packing_statistics", false], [4, "module-moptipyapps.binpacking2d.plot_packing", false], [5, "module-moptipyapps.binpacking2d.encodings", false], [5, "module-moptipyapps.binpacking2d.encodings.ibl_encoding_1", false], [5, "module-moptipyapps.binpacking2d.encodings.ibl_encoding_2", false], [6, "module-moptipyapps.binpacking2d.instgen", false], [6, "module-moptipyapps.binpacking2d.instgen.errors", false], [6, "module-moptipyapps.binpacking2d.instgen.errors_and_hardness", false], [6, "module-moptipyapps.binpacking2d.instgen.experiment", false], [6, "module-moptipyapps.binpacking2d.instgen.hardness", false], [6, "module-moptipyapps.binpacking2d.instgen.inst_decoding", false], [6, "module-moptipyapps.binpacking2d.instgen.instance_space", false], [6, "module-moptipyapps.binpacking2d.instgen.problem", false], [7, "module-moptipyapps.binpacking2d.objectives", false], [7, "module-moptipyapps.binpacking2d.objectives.bin_count", false], [7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_empty", false], [7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_last_empty", false], [7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline", false], [7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_last_small", false], [7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline", false], [7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_small", false], [8, "module-moptipyapps.dynamic_control", false], [8, "module-moptipyapps.dynamic_control.controller", false], [8, "module-moptipyapps.dynamic_control.experiment_raw", false], [8, "module-moptipyapps.dynamic_control.experiment_surrogate", false], [8, "module-moptipyapps.dynamic_control.instance", false], [8, "module-moptipyapps.dynamic_control.model_objective", false], [8, "module-moptipyapps.dynamic_control.objective", false], [8, "module-moptipyapps.dynamic_control.ode", false], [8, "module-moptipyapps.dynamic_control.results_log", false], [8, "module-moptipyapps.dynamic_control.results_plot", false], [8, "module-moptipyapps.dynamic_control.starting_points", false], [8, "module-moptipyapps.dynamic_control.surrogate_optimizer", false], [8, "module-moptipyapps.dynamic_control.system", false], [8, "module-moptipyapps.dynamic_control.system_model", false], [9, "module-moptipyapps.dynamic_control.controllers", false], [9, "module-moptipyapps.dynamic_control.controllers.ann", false], [9, "module-moptipyapps.dynamic_control.controllers.codegen", false], [9, "module-moptipyapps.dynamic_control.controllers.cubic", false], [9, "module-moptipyapps.dynamic_control.controllers.linear", false], [9, "module-moptipyapps.dynamic_control.controllers.min_ann", false], [9, "module-moptipyapps.dynamic_control.controllers.partially_linear", false], [9, "module-moptipyapps.dynamic_control.controllers.peaks", false], [9, "module-moptipyapps.dynamic_control.controllers.predefined", false], [9, "module-moptipyapps.dynamic_control.controllers.quadratic", false], [10, "module-moptipyapps.dynamic_control.systems", false], [10, "module-moptipyapps.dynamic_control.systems.lorenz", false], [10, "module-moptipyapps.dynamic_control.systems.stuart_landau", false], [10, "module-moptipyapps.dynamic_control.systems.three_coupled_oscillators", false], [11, "module-moptipyapps.order1d", false], [11, "module-moptipyapps.order1d.distances", false], [11, "module-moptipyapps.order1d.instance", false], [11, "module-moptipyapps.order1d.space", false], [12, "module-moptipyapps.qap", false], [12, "module-moptipyapps.qap.instance", false], [12, "module-moptipyapps.qap.objective", false], [13, "module-moptipyapps.qap.qaplib", false], [14, "module-moptipyapps.tests", false], [14, "module-moptipyapps.tests.on_binpacking2d", false], [14, "module-moptipyapps.tests.on_tsp", false], [15, "module-moptipyapps.tsp", false], [15, "module-moptipyapps.tsp.ea1p1_revn", false], [15, "module-moptipyapps.tsp.fea1p1_revn", false], [15, "module-moptipyapps.tsp.instance", false], [15, "module-moptipyapps.tsp.known_optima", false], [15, "module-moptipyapps.tsp.tour_length", false], [16, "module-moptipyapps.tsp.tsplib", false], [17, "module-moptipyapps.ttp", false], [17, "module-moptipyapps.ttp.errors", false], [17, "module-moptipyapps.ttp.game_encoding", false], [17, "module-moptipyapps.ttp.game_plan", false], [17, "module-moptipyapps.ttp.game_plan_space", false], [17, "module-moptipyapps.ttp.instance", false], [17, "module-moptipyapps.ttp.plan_length", false], [18, "module-moptipyapps.ttp.robinx", false]], "moptipyapps": [[3, "module-moptipyapps", false]], "moptipyapps.binpacking2d": [[4, "module-moptipyapps.binpacking2d", false]], "moptipyapps.binpacking2d.bks": [[4, "module-moptipyapps.binpacking2d.bks", false]], "moptipyapps.binpacking2d.encodings": [[5, "module-moptipyapps.binpacking2d.encodings", false]], "moptipyapps.binpacking2d.encodings.ibl_encoding_1": [[5, "module-moptipyapps.binpacking2d.encodings.ibl_encoding_1", false]], "moptipyapps.binpacking2d.encodings.ibl_encoding_2": [[5, "module-moptipyapps.binpacking2d.encodings.ibl_encoding_2", false]], "moptipyapps.binpacking2d.experiment": [[4, "module-moptipyapps.binpacking2d.experiment", false]], "moptipyapps.binpacking2d.instance": [[4, "module-moptipyapps.binpacking2d.instance", false]], "moptipyapps.binpacking2d.instgen": [[6, "module-moptipyapps.binpacking2d.instgen", false]], "moptipyapps.binpacking2d.instgen.errors": [[6, "module-moptipyapps.binpacking2d.instgen.errors", false]], "moptipyapps.binpacking2d.instgen.errors_and_hardness": [[6, "module-moptipyapps.binpacking2d.instgen.errors_and_hardness", false]], "moptipyapps.binpacking2d.instgen.experiment": [[6, "module-moptipyapps.binpacking2d.instgen.experiment", false]], "moptipyapps.binpacking2d.instgen.hardness": [[6, "module-moptipyapps.binpacking2d.instgen.hardness", false]], "moptipyapps.binpacking2d.instgen.inst_decoding": [[6, "module-moptipyapps.binpacking2d.instgen.inst_decoding", false]], "moptipyapps.binpacking2d.instgen.instance_space": [[6, "module-moptipyapps.binpacking2d.instgen.instance_space", false]], "moptipyapps.binpacking2d.instgen.problem": [[6, "module-moptipyapps.binpacking2d.instgen.problem", false]], "moptipyapps.binpacking2d.make_instances": [[4, "module-moptipyapps.binpacking2d.make_instances", false]], "moptipyapps.binpacking2d.objectives": [[7, "module-moptipyapps.binpacking2d.objectives", false]], "moptipyapps.binpacking2d.objectives.bin_count": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count", false]], "moptipyapps.binpacking2d.objectives.bin_count_and_empty": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_empty", false]], "moptipyapps.binpacking2d.objectives.bin_count_and_last_empty": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_last_empty", false]], "moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline", false]], "moptipyapps.binpacking2d.objectives.bin_count_and_last_small": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_last_small", false]], "moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline", false]], "moptipyapps.binpacking2d.objectives.bin_count_and_small": [[7, "module-moptipyapps.binpacking2d.objectives.bin_count_and_small", false]], "moptipyapps.binpacking2d.packing": [[4, "module-moptipyapps.binpacking2d.packing", false]], "moptipyapps.binpacking2d.packing_result": [[4, "module-moptipyapps.binpacking2d.packing_result", false]], "moptipyapps.binpacking2d.packing_space": [[4, "module-moptipyapps.binpacking2d.packing_space", false]], "moptipyapps.binpacking2d.packing_statistics": [[4, "module-moptipyapps.binpacking2d.packing_statistics", false]], "moptipyapps.binpacking2d.plot_packing": [[4, "module-moptipyapps.binpacking2d.plot_packing", false]], "moptipyapps.dynamic_control": [[8, "module-moptipyapps.dynamic_control", false]], "moptipyapps.dynamic_control.controller": [[8, "module-moptipyapps.dynamic_control.controller", false]], "moptipyapps.dynamic_control.controllers": [[9, "module-moptipyapps.dynamic_control.controllers", false]], "moptipyapps.dynamic_control.controllers.ann": [[9, "module-moptipyapps.dynamic_control.controllers.ann", false]], "moptipyapps.dynamic_control.controllers.codegen": [[9, "module-moptipyapps.dynamic_control.controllers.codegen", false]], "moptipyapps.dynamic_control.controllers.cubic": [[9, "module-moptipyapps.dynamic_control.controllers.cubic", false]], "moptipyapps.dynamic_control.controllers.linear": [[9, "module-moptipyapps.dynamic_control.controllers.linear", false]], "moptipyapps.dynamic_control.controllers.min_ann": [[9, "module-moptipyapps.dynamic_control.controllers.min_ann", false]], "moptipyapps.dynamic_control.controllers.partially_linear": [[9, "module-moptipyapps.dynamic_control.controllers.partially_linear", false]], "moptipyapps.dynamic_control.controllers.peaks": [[9, "module-moptipyapps.dynamic_control.controllers.peaks", false]], "moptipyapps.dynamic_control.controllers.predefined": [[9, "module-moptipyapps.dynamic_control.controllers.predefined", false]], "moptipyapps.dynamic_control.controllers.quadratic": [[9, "module-moptipyapps.dynamic_control.controllers.quadratic", false]], "moptipyapps.dynamic_control.experiment_raw": [[8, "module-moptipyapps.dynamic_control.experiment_raw", false]], "moptipyapps.dynamic_control.experiment_surrogate": [[8, "module-moptipyapps.dynamic_control.experiment_surrogate", false]], "moptipyapps.dynamic_control.instance": [[8, "module-moptipyapps.dynamic_control.instance", false]], "moptipyapps.dynamic_control.model_objective": [[8, "module-moptipyapps.dynamic_control.model_objective", false]], "moptipyapps.dynamic_control.objective": [[8, "module-moptipyapps.dynamic_control.objective", false]], "moptipyapps.dynamic_control.ode": [[8, "module-moptipyapps.dynamic_control.ode", false]], "moptipyapps.dynamic_control.results_log": [[8, "module-moptipyapps.dynamic_control.results_log", false]], "moptipyapps.dynamic_control.results_plot": [[8, "module-moptipyapps.dynamic_control.results_plot", false]], "moptipyapps.dynamic_control.starting_points": [[8, "module-moptipyapps.dynamic_control.starting_points", false]], "moptipyapps.dynamic_control.surrogate_optimizer": [[8, "module-moptipyapps.dynamic_control.surrogate_optimizer", false]], "moptipyapps.dynamic_control.system": [[8, "module-moptipyapps.dynamic_control.system", false]], "moptipyapps.dynamic_control.system_model": [[8, "module-moptipyapps.dynamic_control.system_model", false]], "moptipyapps.dynamic_control.systems": [[10, "module-moptipyapps.dynamic_control.systems", false]], "moptipyapps.dynamic_control.systems.lorenz": [[10, "module-moptipyapps.dynamic_control.systems.lorenz", false]], "moptipyapps.dynamic_control.systems.stuart_landau": [[10, "module-moptipyapps.dynamic_control.systems.stuart_landau", false]], "moptipyapps.dynamic_control.systems.three_coupled_oscillators": [[10, "module-moptipyapps.dynamic_control.systems.three_coupled_oscillators", false]], "moptipyapps.order1d": [[11, "module-moptipyapps.order1d", false]], "moptipyapps.order1d.distances": [[11, "module-moptipyapps.order1d.distances", false]], "moptipyapps.order1d.instance": [[11, "module-moptipyapps.order1d.instance", false]], "moptipyapps.order1d.space": [[11, "module-moptipyapps.order1d.space", false]], "moptipyapps.qap": [[12, "module-moptipyapps.qap", false]], "moptipyapps.qap.instance": [[12, "module-moptipyapps.qap.instance", false]], "moptipyapps.qap.objective": [[12, "module-moptipyapps.qap.objective", false]], "moptipyapps.qap.qaplib": [[13, "module-moptipyapps.qap.qaplib", false]], "moptipyapps.shared": [[3, "module-moptipyapps.shared", false]], "moptipyapps.tests": [[14, "module-moptipyapps.tests", false]], "moptipyapps.tests.on_binpacking2d": [[14, "module-moptipyapps.tests.on_binpacking2d", false]], "moptipyapps.tests.on_tsp": [[14, "module-moptipyapps.tests.on_tsp", false]], "moptipyapps.tsp": [[15, "module-moptipyapps.tsp", false]], "moptipyapps.tsp.ea1p1_revn": [[15, "module-moptipyapps.tsp.ea1p1_revn", false]], "moptipyapps.tsp.fea1p1_revn": [[15, "module-moptipyapps.tsp.fea1p1_revn", false]], "moptipyapps.tsp.instance": [[15, "module-moptipyapps.tsp.instance", false]], "moptipyapps.tsp.known_optima": [[15, "module-moptipyapps.tsp.known_optima", false]], "moptipyapps.tsp.tour_length": [[15, "module-moptipyapps.tsp.tour_length", false]], "moptipyapps.tsp.tsplib": [[16, "module-moptipyapps.tsp.tsplib", false]], "moptipyapps.ttp": [[17, "module-moptipyapps.ttp", false]], "moptipyapps.ttp.errors": [[17, "module-moptipyapps.ttp.errors", false]], "moptipyapps.ttp.game_encoding": [[17, "module-moptipyapps.ttp.game_encoding", false]], "moptipyapps.ttp.game_plan": [[17, "module-moptipyapps.ttp.game_plan", false]], "moptipyapps.ttp.game_plan_space": [[17, "module-moptipyapps.ttp.game_plan_space", false]], "moptipyapps.ttp.instance": [[17, "module-moptipyapps.ttp.instance", false]], "moptipyapps.ttp.plan_length": [[17, "module-moptipyapps.ttp.plan_length", false]], "moptipyapps.ttp.robinx": [[18, "module-moptipyapps.ttp.robinx", false]], "moptipyapps.version": [[3, "module-moptipyapps.version", false]], "moptipyapps_argparser() (in module moptipyapps.shared)": [[3, "moptipyapps.shared.moptipyapps_argparser", false]], "motipyapps_footer_bottom_comments() (in module moptipyapps.shared)": [[3, "moptipyapps.shared.motipyapps_footer_bottom_comments", false]], "ms_for_training (moptipyapps.dynamic_control.surrogate_optimizer.surrogateoptimizer attribute)": [[8, "moptipyapps.dynamic_control.surrogate_optimizer.SurrogateOptimizer.ms_for_training", false]], "ms_per_model_run (moptipyapps.dynamic_control.surrogate_optimizer.surrogateoptimizer attribute)": [[8, "moptipyapps.dynamic_control.surrogate_optimizer.SurrogateOptimizer.ms_per_model_run", false]], "multi_run_ode() (in module moptipyapps.dynamic_control.ode)": [[8, "moptipyapps.dynamic_control.ode.multi_run_ode", false]], "mxga (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.MXGA", false]], "n (moptipyapps.qap.instance.instance attribute)": [[12, "moptipyapps.qap.instance.Instance.n", false]], "n_bins (moptipyapps.binpacking2d.packing.packing attribute)": [[4, "moptipyapps.binpacking2d.packing.Packing.n_bins", false]], "n_cities (moptipyapps.tsp.instance.instance attribute)": [[15, "moptipyapps.tsp.instance.Instance.n_cities", false]], "n_different_items (in module moptipyapps.binpacking2d.instance)": [[4, "moptipyapps.binpacking2d.instance.N_DIFFERENT_ITEMS", false]], "n_different_items (moptipyapps.binpacking2d.instance.instance attribute)": [[4, "moptipyapps.binpacking2d.instance.Instance.n_different_items", false]], "n_different_items (moptipyapps.binpacking2d.instgen.instance_space.instancespace attribute)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.n_different_items", false]], "n_different_items (moptipyapps.binpacking2d.packing_result.packingresult attribute)": [[4, "moptipyapps.binpacking2d.packing_result.PackingResult.n_different_items", false]], "n_different_items (moptipyapps.binpacking2d.packing_statistics.packingstatistics attribute)": [[4, "moptipyapps.binpacking2d.packing_statistics.PackingStatistics.n_different_items", false]], "n_items (in module moptipyapps.binpacking2d.instance)": [[4, "moptipyapps.binpacking2d.instance.N_ITEMS", false]], "n_items (moptipyapps.binpacking2d.instance.instance attribute)": [[4, "moptipyapps.binpacking2d.instance.Instance.n_items", false]], "n_items (moptipyapps.binpacking2d.instgen.instance_space.instancespace attribute)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.n_items", false]], "n_items (moptipyapps.binpacking2d.packing_result.packingresult attribute)": [[4, "moptipyapps.binpacking2d.packing_result.PackingResult.n_items", false]], "n_items (moptipyapps.binpacking2d.packing_statistics.packingstatistics attribute)": [[4, "moptipyapps.binpacking2d.packing_statistics.PackingStatistics.n_items", false]], "n_points() (moptipyapps.binpacking2d.instgen.instance_space.instancespace method)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.n_points", false]], "n_points() (moptipyapps.binpacking2d.packing_space.packingspace method)": [[4, "moptipyapps.binpacking2d.packing_space.PackingSpace.n_points", false]], "n_points() (moptipyapps.ttp.game_plan_space.gameplanspace method)": [[17, "moptipyapps.ttp.game_plan_space.GamePlanSpace.n_points", false]], "n_runs (moptipyapps.binpacking2d.instgen.hardness.hardness attribute)": [[6, "moptipyapps.binpacking2d.instgen.hardness.Hardness.n_runs", false]], "name (moptipyapps.binpacking2d.bks.element attribute)": [[4, "moptipyapps.binpacking2d.bks.Element.name", false]], "name (moptipyapps.binpacking2d.instance.instance attribute)": [[4, "moptipyapps.binpacking2d.instance.Instance.name", false]], "name (moptipyapps.dynamic_control.controller.controller attribute)": [[8, "moptipyapps.dynamic_control.controller.Controller.name", false]], "name (moptipyapps.dynamic_control.instance.instance attribute)": [[8, "moptipyapps.dynamic_control.instance.Instance.name", false]], "name (moptipyapps.dynamic_control.system.system attribute)": [[8, "moptipyapps.dynamic_control.system.System.name", false]], "name (moptipyapps.qap.instance.instance attribute)": [[12, "moptipyapps.qap.instance.Instance.name", false]], "name (moptipyapps.tsp.instance.instance attribute)": [[15, "moptipyapps.tsp.instance.Instance.name", false]], "name_suffix (moptipyapps.binpacking2d.bks.element attribute)": [[4, "moptipyapps.binpacking2d.bks.Element.name_suffix", false]], "ncities_from_tsplib_name() (in module moptipyapps.tsp.instance)": [[15, "moptipyapps.tsp.instance.ncities_from_tsplib_name", false]], "no_ref (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.NO_REF", false]], "objective_bounds (moptipyapps.binpacking2d.packing_result.packingresult attribute)": [[4, "moptipyapps.binpacking2d.packing_result.PackingResult.objective_bounds", false]], "objective_bounds (moptipyapps.binpacking2d.packing_statistics.packingstatistics attribute)": [[4, "moptipyapps.binpacking2d.packing_statistics.PackingStatistics.objective_bounds", false]], "objectives (moptipyapps.binpacking2d.packing_result.packingresult attribute)": [[4, "moptipyapps.binpacking2d.packing_result.PackingResult.objectives", false]], "objectives (moptipyapps.binpacking2d.packing_statistics.packingstatistics attribute)": [[4, "moptipyapps.binpacking2d.packing_statistics.PackingStatistics.objectives", false]], "on_completion() (in module moptipyapps.dynamic_control.experiment_raw)": [[8, "moptipyapps.dynamic_control.experiment_raw.on_completion", false]], "on_completion() (in module moptipyapps.dynamic_control.experiment_surrogate)": [[8, "moptipyapps.dynamic_control.experiment_surrogate.on_completion", false]], "open_resource_stream() (in module moptipyapps.qap.qaplib)": [[13, "moptipyapps.qap.qaplib.open_resource_stream", false]], "open_resource_stream() (in module moptipyapps.tsp.tsplib)": [[16, "moptipyapps.tsp.tsplib.open_resource_stream", false]], "open_resource_stream() (in module moptipyapps.ttp.robinx)": [[18, "moptipyapps.ttp.robinx.open_resource_stream", false]], "opt_tour_from_file() (in module moptipyapps.tsp.known_optima)": [[15, "moptipyapps.tsp.known_optima.opt_tour_from_file", false]], "opt_tour_from_resource() (in module moptipyapps.tsp.known_optima)": [[15, "moptipyapps.tsp.known_optima.opt_tour_from_resource", false]], "orderingspace (class in moptipyapps.order1d.space)": [[11, "moptipyapps.order1d.space.OrderingSpace", false]], "pac (in module moptipyapps.binpacking2d.bks)": [[4, "moptipyapps.binpacking2d.bks.PAC", false]], "packing (class in moptipyapps.binpacking2d.packing)": [[4, "moptipyapps.binpacking2d.packing.Packing", false]], "packingresult (class in moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.PackingResult", false]], "packingspace (class in moptipyapps.binpacking2d.packing_space)": [[4, "moptipyapps.binpacking2d.packing_space.PackingSpace", false]], "packingstatistics (class in moptipyapps.binpacking2d.packing_statistics)": [[4, "moptipyapps.binpacking2d.packing_statistics.PackingStatistics", false]], "param_dims (moptipyapps.dynamic_control.controller.controller attribute)": [[8, "moptipyapps.dynamic_control.controller.Controller.param_dims", false]], "parameter_space() (moptipyapps.dynamic_control.controller.controller method)": [[8, "moptipyapps.dynamic_control.controller.Controller.parameter_space", false]], "parse_row() (moptipyapps.binpacking2d.packing_result.csvreader method)": [[4, "moptipyapps.binpacking2d.packing_result.CsvReader.parse_row", false]], "parse_row() (moptipyapps.binpacking2d.packing_statistics.csvreader method)": [[4, "moptipyapps.binpacking2d.packing_statistics.CsvReader.parse_row", false]], "partially_linear() (in module moptipyapps.dynamic_control.controllers.partially_linear)": [[9, "moptipyapps.dynamic_control.controllers.partially_linear.partially_linear", false]], "peaks() (in module moptipyapps.dynamic_control.controllers.peaks)": [[9, "moptipyapps.dynamic_control.controllers.peaks.peaks", false]], "phi (in module moptipyapps.dynamic_control.controllers.min_ann)": [[9, "moptipyapps.dynamic_control.controllers.min_ann.PHI", false]], "plot_examples (moptipyapps.dynamic_control.system.system attribute)": [[8, "moptipyapps.dynamic_control.system.System.plot_examples", false]], "plot_packing() (in module moptipyapps.binpacking2d.plot_packing)": [[4, "moptipyapps.binpacking2d.plot_packing.plot_packing", false]], "plot_points() (moptipyapps.dynamic_control.system.system method)": [[8, "moptipyapps.dynamic_control.system.System.plot_points", false]], "predefined() (in module moptipyapps.dynamic_control.controllers.predefined)": [[9, "moptipyapps.dynamic_control.controllers.predefined.predefined", false]], "problem (class in moptipyapps.binpacking2d.instgen.problem)": [[6, "moptipyapps.binpacking2d.instgen.problem.Problem", false]], "qapobjective (class in moptipyapps.qap.objective)": [[12, "moptipyapps.qap.objective.QAPObjective", false]], "quadratic() (in module moptipyapps.dynamic_control.controllers.quadratic)": [[9, "moptipyapps.dynamic_control.controllers.quadratic.quadratic", false]], "reference (moptipyapps.binpacking2d.bks.element attribute)": [[4, "moptipyapps.binpacking2d.bks.Element.reference", false]], "resultslog (class in moptipyapps.dynamic_control.results_log)": [[8, "moptipyapps.dynamic_control.results_log.ResultsLog", false]], "resultsplot (class in moptipyapps.dynamic_control.results_plot)": [[8, "moptipyapps.dynamic_control.results_plot.ResultsPlot", false]], "rev_if_h_not_worse() (in module moptipyapps.tsp.fea1p1_revn)": [[15, "moptipyapps.tsp.fea1p1_revn.rev_if_h_not_worse", false]], "rev_if_not_worse() (in module moptipyapps.tsp.ea1p1_revn)": [[15, "moptipyapps.tsp.ea1p1_revn.rev_if_not_worse", false]], "rls() (in module moptipyapps.binpacking2d.experiment)": [[4, "moptipyapps.binpacking2d.experiment.rls", false]], "rounds (moptipyapps.ttp.instance.instance attribute)": [[17, "moptipyapps.ttp.instance.Instance.rounds", false]], "run() (in module moptipyapps.binpacking2d.experiment)": [[4, "moptipyapps.binpacking2d.experiment.run", false]], "run() (in module moptipyapps.binpacking2d.instgen.experiment)": [[6, "moptipyapps.binpacking2d.instgen.experiment.run", false]], "run() (in module moptipyapps.dynamic_control.experiment_raw)": [[8, "moptipyapps.dynamic_control.experiment_raw.run", false]], "run() (in module moptipyapps.dynamic_control.experiment_surrogate)": [[8, "moptipyapps.dynamic_control.experiment_surrogate.run", false]], "run_ode() (in module moptipyapps.dynamic_control.ode)": [[8, "moptipyapps.dynamic_control.ode.run_ode", false]], "scope (moptipyapps.binpacking2d.packing_result.csvwriter attribute)": [[4, "moptipyapps.binpacking2d.packing_result.CsvWriter.scope", false]], "scope (moptipyapps.binpacking2d.packing_statistics.csvwriter attribute)": [[4, "moptipyapps.binpacking2d.packing_statistics.CsvWriter.scope", false]], "scope_instance (in module moptipyapps.shared)": [[3, "moptipyapps.shared.SCOPE_INSTANCE", false]], "search_space (moptipyapps.binpacking2d.instgen.problem.problem attribute)": [[6, "moptipyapps.binpacking2d.instgen.problem.Problem.search_space", false]], "search_space() (moptipyapps.ttp.game_encoding.gameencoding method)": [[17, "moptipyapps.ttp.game_encoding.GameEncoding.search_space", false]], "search_space_for_n_and_rounds() (in module moptipyapps.ttp.game_encoding)": [[17, "moptipyapps.ttp.game_encoding.search_space_for_n_and_rounds", false]], "separation_max (moptipyapps.ttp.instance.instance attribute)": [[17, "moptipyapps.ttp.instance.Instance.separation_max", false]], "separation_min (moptipyapps.ttp.instance.instance attribute)": [[17, "moptipyapps.ttp.instance.Instance.separation_min", false]], "set_model() (moptipyapps.dynamic_control.objective.figureofmerit method)": [[8, "moptipyapps.dynamic_control.objective.FigureOfMerit.set_model", false]], "set_raw() (moptipyapps.dynamic_control.objective.figureofmerit method)": [[8, "moptipyapps.dynamic_control.objective.FigureOfMerit.set_raw", false]], "setup() (moptipyapps.binpacking2d.packing_result.csvwriter method)": [[4, "moptipyapps.binpacking2d.packing_result.CsvWriter.setup", false]], "setup() (moptipyapps.binpacking2d.packing_statistics.csvwriter method)": [[4, "moptipyapps.binpacking2d.packing_statistics.CsvWriter.setup", false]], "setup_rls_f1() (in module moptipyapps.binpacking2d.instgen.hardness)": [[6, "moptipyapps.binpacking2d.instgen.hardness.setup_rls_f1", false]], "setup_rls_f7() (in module moptipyapps.binpacking2d.instgen.hardness)": [[6, "moptipyapps.binpacking2d.instgen.hardness.setup_rls_f7", false]], "setup_rs_f1() (in module moptipyapps.binpacking2d.instgen.hardness)": [[6, "moptipyapps.binpacking2d.instgen.hardness.setup_rs_f1", false]], "solution_space (moptipyapps.binpacking2d.instgen.problem.problem attribute)": [[6, "moptipyapps.binpacking2d.instgen.problem.Problem.solution_space", false]], "solve() (moptipyapps.dynamic_control.surrogate_optimizer.surrogateoptimizer method)": [[8, "moptipyapps.dynamic_control.surrogate_optimizer.SurrogateOptimizer.solve", false]], "solve() (moptipyapps.tsp.ea1p1_revn.tspea1p1revn method)": [[15, "moptipyapps.tsp.ea1p1_revn.TSPEA1p1revn.solve", false]], "solve() (moptipyapps.tsp.fea1p1_revn.tspfea1p1revn method)": [[15, "moptipyapps.tsp.fea1p1_revn.TSPFEA1p1revn.solve", false]], "space (moptipyapps.binpacking2d.instgen.errors.errors attribute)": [[6, "moptipyapps.binpacking2d.instgen.errors.Errors.space", false]], "space (moptipyapps.binpacking2d.instgen.inst_decoding.instancedecoder attribute)": [[6, "moptipyapps.binpacking2d.instgen.inst_decoding.InstanceDecoder.space", false]], "start_color (in module moptipyapps.dynamic_control.results_plot)": [[8, "moptipyapps.dynamic_control.results_plot.START_COLOR", false]], "start_marker (in module moptipyapps.dynamic_control.results_plot)": [[8, "moptipyapps.dynamic_control.results_plot.START_MARKER", false]], "state_dim_mod (moptipyapps.dynamic_control.system.system attribute)": [[8, "moptipyapps.dynamic_control.system.System.state_dim_mod", false]], "state_dims (moptipyapps.dynamic_control.controller.controller attribute)": [[8, "moptipyapps.dynamic_control.controller.Controller.state_dims", false]], "state_dims (moptipyapps.dynamic_control.system.system attribute)": [[8, "moptipyapps.dynamic_control.system.System.state_dims", false]], "state_dims_in_j (moptipyapps.dynamic_control.system.system attribute)": [[8, "moptipyapps.dynamic_control.system.System.state_dims_in_j", false]], "stuart_landau_111 (in module moptipyapps.dynamic_control.systems.stuart_landau)": [[10, "moptipyapps.dynamic_control.systems.stuart_landau.STUART_LANDAU_111", false]], "stuart_landau_4 (in module moptipyapps.dynamic_control.systems.stuart_landau)": [[10, "moptipyapps.dynamic_control.systems.stuart_landau.STUART_LANDAU_4", false]], "sum_up_results() (moptipyapps.dynamic_control.objective.figureofmerit method)": [[8, "moptipyapps.dynamic_control.objective.FigureOfMerit.sum_up_results", false]], "sum_up_results() (moptipyapps.dynamic_control.objective.figureofmeritle method)": [[8, "moptipyapps.dynamic_control.objective.FigureOfMeritLE.sum_up_results", false]], "surrogateoptimizer (class in moptipyapps.dynamic_control.surrogate_optimizer)": [[8, "moptipyapps.dynamic_control.surrogate_optimizer.SurrogateOptimizer", false]], "swap_distance() (in module moptipyapps.order1d.distances)": [[11, "moptipyapps.order1d.distances.swap_distance", false]], "system (class in moptipyapps.dynamic_control.system)": [[8, "moptipyapps.dynamic_control.system.System", false]], "system (moptipyapps.dynamic_control.instance.instance attribute)": [[8, "moptipyapps.dynamic_control.instance.Instance.system", false]], "system_model (moptipyapps.dynamic_control.surrogate_optimizer.surrogateoptimizer attribute)": [[8, "moptipyapps.dynamic_control.surrogate_optimizer.SurrogateOptimizer.system_model", false]], "systemmodel (class in moptipyapps.dynamic_control.system_model)": [[8, "moptipyapps.dynamic_control.system_model.SystemModel", false]], "t (class in moptipyapps.dynamic_control.ode)": [[8, "moptipyapps.dynamic_control.ode.T", false]], "t (class in moptipyapps.dynamic_control.system)": [[8, "moptipyapps.dynamic_control.system.T", false]], "t (class in moptipyapps.order1d.instance)": [[11, "moptipyapps.order1d.instance.T", false]], "t_from_ode() (in module moptipyapps.dynamic_control.ode)": [[8, "moptipyapps.dynamic_control.ode.t_from_ode", false]], "tag_titles (moptipyapps.order1d.instance.instance attribute)": [[11, "moptipyapps.order1d.instance.Instance.tag_titles", false]], "tags (moptipyapps.order1d.instance.instance attribute)": [[11, "moptipyapps.order1d.instance.Instance.tags", false]], "teams (moptipyapps.ttp.instance.instance attribute)": [[17, "moptipyapps.ttp.instance.Instance.teams", false]], "test_starting_states (moptipyapps.dynamic_control.system.system attribute)": [[8, "moptipyapps.dynamic_control.system.System.test_starting_states", false]], "test_steps (moptipyapps.dynamic_control.system.system attribute)": [[8, "moptipyapps.dynamic_control.system.System.test_steps", false]], "test_time (moptipyapps.dynamic_control.system.system attribute)": [[8, "moptipyapps.dynamic_control.system.System.test_time", false]], "three_coupled_oscillators (in module moptipyapps.dynamic_control.systems.three_coupled_oscillators)": [[10, "moptipyapps.dynamic_control.systems.three_coupled_oscillators.THREE_COUPLED_OSCILLATORS", false]], "to_bin_count() (moptipyapps.binpacking2d.objectives.bin_count.bincount method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count.BinCount.to_bin_count", false]], "to_bin_count() (moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.bincountandlastempty method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty.to_bin_count", false]], "to_bin_count() (moptipyapps.binpacking2d.objectives.bin_count_and_last_small.bincountandlastsmall method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall.to_bin_count", false]], "to_compact_str() (moptipyapps.binpacking2d.instance.instance method)": [[4, "moptipyapps.binpacking2d.instance.Instance.to_compact_str", false]], "to_csv() (in module moptipyapps.binpacking2d.packing_result)": [[4, "moptipyapps.binpacking2d.packing_result.to_csv", false]], "to_csv() (in module moptipyapps.binpacking2d.packing_statistics)": [[4, "moptipyapps.binpacking2d.packing_statistics.to_csv", false]], "to_str() (moptipyapps.binpacking2d.instgen.instance_space.instancespace method)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.to_str", false]], "to_str() (moptipyapps.binpacking2d.packing_space.packingspace method)": [[4, "moptipyapps.binpacking2d.packing_space.PackingSpace.to_str", false]], "to_str() (moptipyapps.order1d.space.orderingspace method)": [[11, "moptipyapps.order1d.space.OrderingSpace.to_str", false]], "to_stream() (moptipyapps.tsp.instance.instance method)": [[15, "moptipyapps.tsp.instance.Instance.to_stream", false]], "total_item_area (moptipyapps.binpacking2d.instance.instance attribute)": [[4, "moptipyapps.binpacking2d.instance.Instance.total_item_area", false]], "total_item_area (moptipyapps.binpacking2d.instgen.instance_space.instancespace attribute)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.total_item_area", false]], "tour_length() (in module moptipyapps.tsp.tour_length)": [[15, "moptipyapps.tsp.tour_length.tour_length", false]], "tour_length_lower_bound (moptipyapps.tsp.instance.instance attribute)": [[15, "moptipyapps.tsp.instance.Instance.tour_length_lower_bound", false]], "tour_length_upper_bound (moptipyapps.tsp.instance.instance attribute)": [[15, "moptipyapps.tsp.instance.Instance.tour_length_upper_bound", false]], "tourlength (class in moptipyapps.tsp.tour_length)": [[15, "moptipyapps.tsp.tour_length.TourLength", false]], "training_starting_states (moptipyapps.dynamic_control.system.system attribute)": [[8, "moptipyapps.dynamic_control.system.System.training_starting_states", false]], "training_steps (moptipyapps.dynamic_control.system.system attribute)": [[8, "moptipyapps.dynamic_control.system.System.training_steps", false]], "training_time (moptipyapps.dynamic_control.system.system attribute)": [[8, "moptipyapps.dynamic_control.system.System.training_time", false]], "trivial_bounds() (in module moptipyapps.qap.instance)": [[12, "moptipyapps.qap.instance.trivial_bounds", false]], "tsp_instances_for_tests() (in module moptipyapps.tests.on_tsp)": [[14, "moptipyapps.tests.on_tsp.tsp_instances_for_tests", false]], "tspea1p1revn (class in moptipyapps.tsp.ea1p1_revn)": [[15, "moptipyapps.tsp.ea1p1_revn.TSPEA1p1revn", false]], "tspfea1p1revn (class in moptipyapps.tsp.fea1p1_revn)": [[15, "moptipyapps.tsp.fea1p1_revn.TSPFEA1p1revn", false]], "unindent() (moptipyapps.dynamic_control.controllers.codegen.codegenerator method)": [[9, "moptipyapps.dynamic_control.controllers.codegen.CodeGenerator.unindent", false]], "upper_bound (moptipyapps.qap.instance.instance attribute)": [[12, "moptipyapps.qap.instance.Instance.upper_bound", false]], "upper_bound() (moptipyapps.binpacking2d.instgen.errors.errors method)": [[6, "moptipyapps.binpacking2d.instgen.errors.Errors.upper_bound", false]], "upper_bound() (moptipyapps.binpacking2d.instgen.errors_and_hardness.errorsandhardness method)": [[6, "moptipyapps.binpacking2d.instgen.errors_and_hardness.ErrorsAndHardness.upper_bound", false]], "upper_bound() (moptipyapps.binpacking2d.instgen.hardness.hardness method)": [[6, "moptipyapps.binpacking2d.instgen.hardness.Hardness.upper_bound", false]], "upper_bound() (moptipyapps.binpacking2d.objectives.bin_count.bincount method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count.BinCount.upper_bound", false]], "upper_bound() (moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.bincountandlastempty method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty.upper_bound", false]], "upper_bound() (moptipyapps.binpacking2d.objectives.bin_count_and_last_small.bincountandlastsmall method)": [[7, "moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall.upper_bound", false]], "upper_bound() (moptipyapps.qap.objective.qapobjective method)": [[12, "moptipyapps.qap.objective.QAPObjective.upper_bound", false]], "upper_bound() (moptipyapps.tsp.tour_length.tourlength method)": [[15, "moptipyapps.tsp.tour_length.TourLength.upper_bound", false]], "upper_bound() (moptipyapps.ttp.errors.errors method)": [[17, "moptipyapps.ttp.errors.Errors.upper_bound", false]], "upper_bound() (moptipyapps.ttp.plan_length.gameplanlength method)": [[17, "moptipyapps.ttp.plan_length.GamePlanLength.upper_bound", false]], "validate() (moptipyapps.binpacking2d.instgen.instance_space.instancespace method)": [[6, "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace.validate", false]], "validate() (moptipyapps.binpacking2d.packing_space.packingspace method)": [[4, "moptipyapps.binpacking2d.packing_space.PackingSpace.validate", false]], "validate() (moptipyapps.ttp.game_plan_space.gameplanspace method)": [[17, "moptipyapps.ttp.game_plan_space.GamePlanSpace.validate", false]], "validate_algorithm_on_1_2dbinpacking() (in module moptipyapps.tests.on_binpacking2d)": [[14, "moptipyapps.tests.on_binpacking2d.validate_algorithm_on_1_2dbinpacking", false]], "validate_algorithm_on_1_tsp() (in module moptipyapps.tests.on_tsp)": [[14, "moptipyapps.tests.on_tsp.validate_algorithm_on_1_tsp", false]], "validate_algorithm_on_2dbinpacking() (in module moptipyapps.tests.on_binpacking2d)": [[14, "moptipyapps.tests.on_binpacking2d.validate_algorithm_on_2dbinpacking", false]], "validate_algorithm_on_tsp() (in module moptipyapps.tests.on_tsp)": [[14, "moptipyapps.tests.on_tsp.validate_algorithm_on_tsp", false]], "validate_objective_on_1_2dbinpacking() (in module moptipyapps.tests.on_binpacking2d)": [[14, "moptipyapps.tests.on_binpacking2d.validate_objective_on_1_2dbinpacking", false]], "validate_objective_on_1_tsp() (in module moptipyapps.tests.on_tsp)": [[14, "moptipyapps.tests.on_tsp.validate_objective_on_1_tsp", false]], "validate_objective_on_2dbinpacking() (in module moptipyapps.tests.on_binpacking2d)": [[14, "moptipyapps.tests.on_binpacking2d.validate_objective_on_2dbinpacking", false]], "validate_objective_on_tsp() (in module moptipyapps.tests.on_tsp)": [[14, "moptipyapps.tests.on_tsp.validate_objective_on_tsp", false]], "validate_signed_permutation_encoding_on_1_2dbinpacking() (in module moptipyapps.tests.on_binpacking2d)": [[14, "moptipyapps.tests.on_binpacking2d.validate_signed_permutation_encoding_on_1_2dbinpacking", false]], "validate_signed_permutation_encoding_on_2dbinpacking() (in module moptipyapps.tests.on_binpacking2d)": [[14, "moptipyapps.tests.on_binpacking2d.validate_signed_permutation_encoding_on_2dbinpacking", false]], "write() (moptipyapps.dynamic_control.controllers.codegen.codegenerator method)": [[9, "moptipyapps.dynamic_control.controllers.codegen.CodeGenerator.write", false]], "writeln() (moptipyapps.dynamic_control.controllers.codegen.codegenerator method)": [[9, "moptipyapps.dynamic_control.controllers.codegen.CodeGenerator.writeln", false]]}, "objects": {"": [[3, 0, 0, "-", "moptipyapps"]], "moptipyapps": [[4, 0, 0, "-", "binpacking2d"], [8, 0, 0, "-", "dynamic_control"], [11, 0, 0, "-", "order1d"], [12, 0, 0, "-", "qap"], [3, 0, 0, "-", "shared"], [14, 0, 0, "-", "tests"], [15, 0, 0, "-", "tsp"], [17, 0, 0, "-", "ttp"], [3, 0, 0, "-", "version"]], "moptipyapps.binpacking2d": [[4, 0, 0, "-", "bks"], [5, 0, 0, "-", "encodings"], [4, 0, 0, "-", "experiment"], [4, 0, 0, "-", "instance"], [6, 0, 0, "-", "instgen"], [4, 0, 0, "-", "make_instances"], [7, 0, 0, "-", "objectives"], [4, 0, 0, "-", "packing"], [4, 0, 0, "-", "packing_result"], [4, 0, 0, "-", "packing_space"], [4, 0, 0, "-", "packing_statistics"], [4, 0, 0, "-", "plot_packing"]], "moptipyapps.binpacking2d.bks": [[4, 1, 1, "", "ASQAS"], [4, 1, 1, "", "A_LARGE"], [4, 1, 1, "", "A_MED"], [4, 1, 1, "", "A_SMALL"], [4, 1, 1, "", "BENG_1_8"], [4, 1, 1, "", "BENG_9_10"], [4, 1, 1, "", "BRKGA_BPP_2R"], [4, 1, 1, "", "BRKGA_BPP_ANB"], [4, 1, 1, "", "CLASS_10_100"], [4, 1, 1, "", "CLASS_10_20"], [4, 1, 1, "", "CLASS_10_40"], [4, 1, 1, "", "CLASS_10_60"], [4, 1, 1, "", "CLASS_10_80"], [4, 1, 1, "", "CLASS_1_100"], [4, 1, 1, "", "CLASS_1_20"], [4, 1, 1, "", "CLASS_1_40"], [4, 1, 1, "", "CLASS_1_60"], [4, 1, 1, "", "CLASS_1_80"], [4, 1, 1, "", "CLASS_2_100"], [4, 1, 1, "", "CLASS_2_20"], [4, 1, 1, "", "CLASS_2_40"], [4, 1, 1, "", "CLASS_2_60"], [4, 1, 1, "", "CLASS_2_80"], [4, 1, 1, "", "CLASS_3_100"], [4, 1, 1, "", "CLASS_3_20"], [4, 1, 1, "", "CLASS_3_40"], [4, 1, 1, "", "CLASS_3_60"], [4, 1, 1, "", "CLASS_3_80"], [4, 1, 1, "", "CLASS_4_100"], [4, 1, 1, "", "CLASS_4_20"], [4, 1, 1, "", "CLASS_4_40"], [4, 1, 1, "", "CLASS_4_60"], [4, 1, 1, "", "CLASS_4_80"], [4, 1, 1, "", "CLASS_5_100"], [4, 1, 1, "", "CLASS_5_20"], [4, 1, 1, "", "CLASS_5_40"], [4, 1, 1, "", "CLASS_5_60"], [4, 1, 1, "", "CLASS_5_80"], [4, 1, 1, "", "CLASS_6_100"], [4, 1, 1, "", "CLASS_6_20"], [4, 1, 1, "", "CLASS_6_40"], [4, 1, 1, "", "CLASS_6_60"], [4, 1, 1, "", "CLASS_6_80"], [4, 1, 1, "", "CLASS_7_100"], [4, 1, 1, "", "CLASS_7_20"], [4, 1, 1, "", "CLASS_7_40"], [4, 1, 1, "", "CLASS_7_60"], [4, 1, 1, "", "CLASS_7_80"], [4, 1, 1, "", "CLASS_8_100"], [4, 1, 1, "", "CLASS_8_20"], [4, 1, 1, "", "CLASS_8_40"], [4, 1, 1, "", "CLASS_8_60"], [4, 1, 1, "", "CLASS_8_80"], [4, 1, 1, "", "CLASS_9_100"], [4, 1, 1, "", "CLASS_9_20"], [4, 1, 1, "", "CLASS_9_40"], [4, 1, 1, "", "CLASS_9_60"], [4, 1, 1, "", "CLASS_9_80"], [4, 1, 1, "", "CLASS_AND_BENG"], [4, 1, 1, "", "EALGFI"], [4, 2, 1, "", "Element"], [4, 1, 1, "", "GRASP_VND"], [4, 1, 1, "", "GROUPS_TO_INSTANCES"], [4, 1, 1, "", "HHANO_R"], [4, 1, 1, "", "HHANO_SR"], [4, 1, 1, "", "INSTANCES_TO_GROUPS"], [4, 5, 1, "", "INST_GROUP_SORT_KEY"], [4, 1, 1, "", "MXGA"], [4, 1, 1, "", "NO_REF"], [4, 1, 1, "", "PAC"], [4, 5, 1, "", "get_related_work"], [4, 5, 1, "", "make_comparison_table"], [4, 5, 1, "", "make_comparison_table_data"]], "moptipyapps.binpacking2d.bks.Element": [[4, 3, 1, "", "get_bibtex"], [4, 4, 1, "", "name"], [4, 4, 1, "", "name_suffix"], [4, 4, 1, "", "reference"]], "moptipyapps.binpacking2d.encodings": [[5, 0, 0, "-", "ibl_encoding_1"], [5, 0, 0, "-", "ibl_encoding_2"]], "moptipyapps.binpacking2d.encodings.ibl_encoding_1": [[5, 2, 1, "", "ImprovedBottomLeftEncoding1"]], "moptipyapps.binpacking2d.encodings.ibl_encoding_1.ImprovedBottomLeftEncoding1": [[5, 3, 1, "", "decode"], [5, 3, 1, "", "log_parameters_to"]], "moptipyapps.binpacking2d.encodings.ibl_encoding_2": [[5, 2, 1, "", "ImprovedBottomLeftEncoding2"]], "moptipyapps.binpacking2d.encodings.ibl_encoding_2.ImprovedBottomLeftEncoding2": [[5, 3, 1, "", "decode"], [5, 3, 1, "", "log_parameters_to"]], "moptipyapps.binpacking2d.experiment": [[4, 1, 1, "", "MAX_FES"], [4, 5, 1, "", "base_setup"], [4, 5, 1, "", "fea"], [4, 5, 1, "", "rls"], [4, 5, 1, "", "run"]], "moptipyapps.binpacking2d.instance": [[4, 1, 1, "", "BIN_HEIGHT"], [4, 1, 1, "", "BIN_WIDTH"], [4, 1, 1, "", "IDX_HEIGHT"], [4, 1, 1, "", "IDX_REPETITION"], [4, 1, 1, "", "IDX_WIDTH"], [4, 1, 1, "", "INSTANCES_RESOURCE"], [4, 1, 1, "", "INTERNAL_SEP"], [4, 2, 1, "", "Instance"], [4, 1, 1, "", "N_DIFFERENT_ITEMS"], [4, 1, 1, "", "N_ITEMS"]], "moptipyapps.binpacking2d.instance.Instance": [[4, 4, 1, "", "bin_height"], [4, 4, 1, "", "bin_width"], [4, 3, 1, "", "from_2dpacklib"], [4, 3, 1, "", "from_compact_str"], [4, 3, 1, "", "from_resource"], [4, 3, 1, "", "get_standard_item_sequence"], [4, 3, 1, "", "list_resources"], [4, 3, 1, "", "list_resources_groups"], [4, 3, 1, "", "log_parameters_to"], [4, 4, 1, "", "lower_bound_bins"], [4, 4, 1, "", "n_different_items"], [4, 4, 1, "", "n_items"], [4, 4, 1, "", "name"], [4, 3, 1, "", "to_compact_str"], [4, 4, 1, "", "total_item_area"]], "moptipyapps.binpacking2d.instgen": [[6, 0, 0, "-", "errors"], [6, 0, 0, "-", "errors_and_hardness"], [6, 0, 0, "-", "experiment"], [6, 0, 0, "-", "hardness"], [6, 0, 0, "-", "inst_decoding"], [6, 0, 0, "-", "instance_space"], [6, 0, 0, "-", "problem"]], "moptipyapps.binpacking2d.instgen.errors": [[6, 2, 1, "", "Errors"]], "moptipyapps.binpacking2d.instgen.errors.Errors": [[6, 3, 1, "", "evaluate"], [6, 3, 1, "", "is_always_integer"], [6, 3, 1, "", "log_parameters_to"], [6, 3, 1, "", "lower_bound"], [6, 4, 1, "", "space"], [6, 3, 1, "", "upper_bound"]], "moptipyapps.binpacking2d.instgen.errors_and_hardness": [[6, 2, 1, "", "ErrorsAndHardness"]], "moptipyapps.binpacking2d.instgen.errors_and_hardness.ErrorsAndHardness": [[6, 4, 1, "", "errors"], [6, 3, 1, "", "evaluate"], [6, 4, 1, "", "hardness"], [6, 3, 1, "", "is_always_integer"], [6, 3, 1, "", "log_parameters_to"], [6, 3, 1, "", "lower_bound"], [6, 3, 1, "", "upper_bound"]], "moptipyapps.binpacking2d.instgen.experiment": [[6, 1, 1, "", "INNER_MAX_FES"], [6, 1, 1, "", "INNER_RUNS"], [6, 1, 1, "", "MAX_FES"], [6, 5, 1, "", "cmaes"], [6, 5, 1, "", "run"]], "moptipyapps.binpacking2d.instgen.hardness": [[6, 1, 1, "", "DEFAULT_EXECUTORS"], [6, 2, 1, "", "Hardness"], [6, 5, 1, "", "setup_rls_f1"], [6, 5, 1, "", "setup_rls_f7"], [6, 5, 1, "", "setup_rs_f1"]], "moptipyapps.binpacking2d.instgen.hardness.Hardness": [[6, 3, 1, "", "evaluate"], [6, 4, 1, "", "executors"], [6, 3, 1, "", "is_always_integer"], [6, 3, 1, "", "log_parameters_to"], [6, 3, 1, "", "lower_bound"], [6, 4, 1, "", "max_fes"], [6, 4, 1, "", "n_runs"], [6, 3, 1, "", "upper_bound"]], "moptipyapps.binpacking2d.instgen.inst_decoding": [[6, 2, 1, "", "InstanceDecoder"]], "moptipyapps.binpacking2d.instgen.inst_decoding.InstanceDecoder": [[6, 3, 1, "", "decode"], [6, 3, 1, "", "get_x_dim"], [6, 4, 1, "", "space"]], "moptipyapps.binpacking2d.instgen.instance_space": [[6, 2, 1, "", "InstanceSpace"]], "moptipyapps.binpacking2d.instgen.instance_space.InstanceSpace": [[6, 4, 1, "", "bin_height"], [6, 4, 1, "", "bin_width"], [6, 3, 1, "", "copy"], [6, 3, 1, "", "create"], [6, 3, 1, "", "from_str"], [6, 4, 1, "", "inst_name"], [6, 3, 1, "", "is_equal"], [6, 4, 1, "", "item_height_max"], [6, 4, 1, "", "item_height_min"], [6, 4, 1, "", "item_width_max"], [6, 4, 1, "", "item_width_min"], [6, 3, 1, "", "log_parameters_to"], [6, 4, 1, "", "min_bins"], [6, 4, 1, "", "n_different_items"], [6, 4, 1, "", "n_items"], [6, 3, 1, "", "n_points"], [6, 3, 1, "", "to_str"], [6, 4, 1, "", "total_item_area"], [6, 3, 1, "", "validate"]], "moptipyapps.binpacking2d.instgen.problem": [[6, 2, 1, "", "Problem"]], "moptipyapps.binpacking2d.instgen.problem.Problem": [[6, 4, 1, "", "encoding"], [6, 4, 1, "", "search_space"], [6, 4, 1, "", "solution_space"]], "moptipyapps.binpacking2d.make_instances": [[4, 5, 1, "", "append_almost_squares_strings"], [4, 5, 1, "", "download_2dpacklib_instances"], [4, 5, 1, "", "join_instances_to_compact"], [4, 5, 1, "", "make_2dpacklib_resource"]], "moptipyapps.binpacking2d.objectives": [[7, 0, 0, "-", "bin_count"], [7, 0, 0, "-", "bin_count_and_empty"], [7, 0, 0, "-", "bin_count_and_last_empty"], [7, 0, 0, "-", "bin_count_and_last_skyline"], [7, 0, 0, "-", "bin_count_and_last_small"], [7, 0, 0, "-", "bin_count_and_lowest_skyline"], [7, 0, 0, "-", "bin_count_and_small"]], "moptipyapps.binpacking2d.objectives.bin_count": [[7, 1, 1, "", "BIN_COUNT_NAME"], [7, 2, 1, "", "BinCount"]], "moptipyapps.binpacking2d.objectives.bin_count.BinCount": [[7, 3, 1, "", "evaluate"], [7, 3, 1, "", "is_always_integer"], [7, 3, 1, "", "lower_bound"], [7, 3, 1, "", "to_bin_count"], [7, 3, 1, "", "upper_bound"]], "moptipyapps.binpacking2d.objectives.bin_count_and_empty": [[7, 2, 1, "", "BinCountAndEmpty"], [7, 5, 1, "", "bin_count_and_empty"]], "moptipyapps.binpacking2d.objectives.bin_count_and_empty.BinCountAndEmpty": [[7, 3, 1, "", "evaluate"]], "moptipyapps.binpacking2d.objectives.bin_count_and_last_empty": [[7, 2, 1, "", "BinCountAndLastEmpty"], [7, 5, 1, "", "bin_count_and_last_empty"]], "moptipyapps.binpacking2d.objectives.bin_count_and_last_empty.BinCountAndLastEmpty": [[7, 3, 1, "", "evaluate"], [7, 3, 1, "", "lower_bound"], [7, 3, 1, "", "to_bin_count"], [7, 3, 1, "", "upper_bound"]], "moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline": [[7, 2, 1, "", "BinCountAndLastSkyline"], [7, 5, 1, "", "bin_count_and_last_skyline"]], "moptipyapps.binpacking2d.objectives.bin_count_and_last_skyline.BinCountAndLastSkyline": [[7, 3, 1, "", "evaluate"]], "moptipyapps.binpacking2d.objectives.bin_count_and_last_small": [[7, 2, 1, "", "BinCountAndLastSmall"], [7, 5, 1, "", "bin_count_and_last_small"]], "moptipyapps.binpacking2d.objectives.bin_count_and_last_small.BinCountAndLastSmall": [[7, 3, 1, "", "evaluate"], [7, 3, 1, "", "lower_bound"], [7, 3, 1, "", "to_bin_count"], [7, 3, 1, "", "upper_bound"]], "moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline": [[7, 2, 1, "", "BinCountAndLowestSkyline"], [7, 5, 1, "", "bin_count_and_lowest_skyline"]], "moptipyapps.binpacking2d.objectives.bin_count_and_lowest_skyline.BinCountAndLowestSkyline": [[7, 3, 1, "", "evaluate"]], "moptipyapps.binpacking2d.objectives.bin_count_and_small": [[7, 2, 1, "", "BinCountAndSmall"], [7, 5, 1, "", "bin_count_and_small"]], "moptipyapps.binpacking2d.objectives.bin_count_and_small.BinCountAndSmall": [[7, 3, 1, "", "evaluate"]], "moptipyapps.binpacking2d.packing": [[4, 1, 1, "", "IDX_BIN"], [4, 1, 1, "", "IDX_BOTTOM_Y"], [4, 1, 1, "", "IDX_ID"], [4, 1, 1, "", "IDX_LEFT_X"], [4, 1, 1, "", "IDX_RIGHT_X"], [4, 1, 1, "", "IDX_TOP_Y"], [4, 2, 1, "", "Packing"]], "moptipyapps.binpacking2d.packing.Packing": [[4, 3, 1, "", "from_log"], [4, 4, 1, "", "instance"], [4, 4, 1, "", "n_bins"]], "moptipyapps.binpacking2d.packing_result": [[4, 2, 1, "", "CsvReader"], [4, 2, 1, "", "CsvWriter"], [4, 1, 1, "", "DEFAULT_OBJECTIVES"], [4, 1, 1, "", "KEY_BIN_HEIGHT"], [4, 1, 1, "", "KEY_BIN_WIDTH"], [4, 1, 1, "", "KEY_N_DIFFERENT_ITEMS"], [4, 1, 1, "", "KEY_N_ITEMS"], [4, 1, 1, "", "LOWER_BOUNDS_BIN_COUNT"], [4, 2, 1, "", "PackingResult"], [4, 5, 1, "", "from_csv"], [4, 5, 1, "", "from_logs"], [4, 5, 1, "", "from_packing_and_end_result"], [4, 5, 1, "", "from_single_log"], [4, 5, 1, "", "to_csv"]], "moptipyapps.binpacking2d.packing_result.CsvReader": [[4, 3, 1, "", "parse_row"]], "moptipyapps.binpacking2d.packing_result.CsvWriter": [[4, 3, 1, "", "get_column_titles"], [4, 3, 1, "", "get_footer_bottom_comments"], [4, 3, 1, "", "get_footer_comments"], [4, 3, 1, "", "get_header_comments"], [4, 3, 1, "", "get_row"], [4, 4, 1, "", "scope"], [4, 3, 1, "", "setup"]], "moptipyapps.binpacking2d.packing_result.PackingResult": [[4, 4, 1, "", "bin_bounds"], [4, 4, 1, "", "bin_height"], [4, 4, 1, "", "bin_width"], [4, 4, 1, "", "end_result"], [4, 4, 1, "", "n_different_items"], [4, 4, 1, "", "n_items"], [4, 4, 1, "", "objective_bounds"], [4, 4, 1, "", "objectives"]], "moptipyapps.binpacking2d.packing_space": [[4, 2, 1, "", "PackingSpace"]], "moptipyapps.binpacking2d.packing_space.PackingSpace": [[4, 3, 1, "", "copy"], [4, 3, 1, "", "create"], [4, 3, 1, "", "from_str"], [4, 4, 1, "", "instance"], [4, 3, 1, "", "is_equal"], [4, 3, 1, "", "log_parameters_to"], [4, 3, 1, "", "n_points"], [4, 3, 1, "", "to_str"], [4, 3, 1, "", "validate"]], "moptipyapps.binpacking2d.packing_statistics": [[4, 2, 1, "", "CsvReader"], [4, 2, 1, "", "CsvWriter"], [4, 2, 1, "", "PackingStatistics"], [4, 5, 1, "", "from_csv"], [4, 5, 1, "", "from_packing_results"], [4, 5, 1, "", "to_csv"]], "moptipyapps.binpacking2d.packing_statistics.CsvReader": [[4, 3, 1, "", "parse_row"]], "moptipyapps.binpacking2d.packing_statistics.CsvWriter": [[4, 3, 1, "", "get_column_titles"], [4, 3, 1, "", "get_footer_bottom_comments"], [4, 3, 1, "", "get_footer_comments"], [4, 3, 1, "", "get_header_comments"], [4, 3, 1, "", "get_row"], [4, 4, 1, "", "scope"], [4, 3, 1, "", "setup"]], "moptipyapps.binpacking2d.packing_statistics.PackingStatistics": [[4, 4, 1, "", "bin_bounds"], [4, 4, 1, "", "bin_height"], [4, 4, 1, "", "bin_width"], [4, 4, 1, "", "end_statistics"], [4, 4, 1, "", "n_different_items"], [4, 4, 1, "", "n_items"], [4, 4, 1, "", "objective_bounds"], [4, 4, 1, "", "objectives"]], "moptipyapps.binpacking2d.plot_packing": [[4, 5, 1, "", "default_packing_item_str"], [4, 5, 1, "", "plot_packing"]], "moptipyapps.dynamic_control": [[8, 0, 0, "-", "controller"], [9, 0, 0, "-", "controllers"], [8, 0, 0, "-", "experiment_raw"], [8, 0, 0, "-", "experiment_surrogate"], [8, 0, 0, "-", "instance"], [8, 0, 0, "-", "model_objective"], [8, 0, 0, "-", "objective"], [8, 0, 0, "-", "ode"], [8, 0, 0, "-", "results_log"], [8, 0, 0, "-", "results_plot"], [8, 0, 0, "-", "starting_points"], [8, 0, 0, "-", "surrogate_optimizer"], [8, 0, 0, "-", "system"], [8, 0, 0, "-", "system_model"], [10, 0, 0, "-", "systems"]], "moptipyapps.dynamic_control.controller": [[8, 2, 1, "", "Controller"]], "moptipyapps.dynamic_control.controller.Controller": [[8, 4, 1, "", "control_dims"], [8, 3, 1, "", "controller"], [8, 3, 1, "", "log_parameters_to"], [8, 4, 1, "", "name"], [8, 4, 1, "", "param_dims"], [8, 3, 1, "", "parameter_space"], [8, 4, 1, "", "state_dims"]], "moptipyapps.dynamic_control.controllers": [[9, 0, 0, "-", "ann"], [9, 0, 0, "-", "codegen"], [9, 0, 0, "-", "cubic"], [9, 0, 0, "-", "linear"], [9, 0, 0, "-", "min_ann"], [9, 0, 0, "-", "partially_linear"], [9, 0, 0, "-", "peaks"], [9, 0, 0, "-", "predefined"], [9, 0, 0, "-", "quadratic"]], "moptipyapps.dynamic_control.controllers.ann": [[9, 5, 1, "", "anns"], [9, 5, 1, "", "make_ann"]], "moptipyapps.dynamic_control.controllers.codegen": [[9, 2, 1, "", "CodeGenerator"]], "moptipyapps.dynamic_control.controllers.codegen.CodeGenerator": [[9, 3, 1, "", "build"], [9, 3, 1, "", "endline"], [9, 3, 1, "", "indent"], [9, 3, 1, "", "unindent"], [9, 3, 1, "", "write"], [9, 3, 1, "", "writeln"]], "moptipyapps.dynamic_control.controllers.cubic": [[9, 5, 1, "", "cubic"]], "moptipyapps.dynamic_control.controllers.linear": [[9, 5, 1, "", "linear"]], "moptipyapps.dynamic_control.controllers.min_ann": [[9, 1, 1, "", "PHI"], [9, 5, 1, "", "min_anns"]], "moptipyapps.dynamic_control.controllers.partially_linear": [[9, 5, 1, "", "partially_linear"]], "moptipyapps.dynamic_control.controllers.peaks": [[9, 5, 1, "", "peaks"]], "moptipyapps.dynamic_control.controllers.predefined": [[9, 5, 1, "", "predefined"]], "moptipyapps.dynamic_control.controllers.quadratic": [[9, 5, 1, "", "quadratic"]], "moptipyapps.dynamic_control.experiment_raw": [[8, 5, 1, "", "base_setup"], [8, 5, 1, "", "cmaes"], [8, 5, 1, "", "make_instances"], [8, 5, 1, "", "on_completion"], [8, 5, 1, "", "run"]], "moptipyapps.dynamic_control.experiment_surrogate": [[8, 1, 1, "", "MAX_FES"], [8, 5, 1, "", "base_setup"], [8, 5, 1, "", "cmaes_raw"], [8, 5, 1, "", "cmaes_surrogate"], [8, 5, 1, "", "make_instances"], [8, 5, 1, "", "on_completion"], [8, 5, 1, "", "run"]], "moptipyapps.dynamic_control.instance": [[8, 2, 1, "", "Instance"]], "moptipyapps.dynamic_control.instance.Instance": [[8, 4, 1, "", "controller"], [8, 3, 1, "", "describe_parameterization"], [8, 3, 1, "", "log_parameters_to"], [8, 4, 1, "", "name"], [8, 4, 1, "", "system"]], "moptipyapps.dynamic_control.model_objective": [[8, 2, 1, "", "ModelObjective"]], "moptipyapps.dynamic_control.model_objective.ModelObjective": [[8, 3, 1, "", "begin"], [8, 3, 1, "", "end"], [8, 3, 1, "", "evaluate"], [8, 3, 1, "", "log_parameters_to"], [8, 3, 1, "", "lower_bound"]], "moptipyapps.dynamic_control.objective": [[8, 2, 1, "", "FigureOfMerit"], [8, 2, 1, "", "FigureOfMeritLE"]], "moptipyapps.dynamic_control.objective.FigureOfMerit": [[8, 3, 1, "", "evaluate"], [8, 3, 1, "", "get_differentials"], [8, 3, 1, "", "initialize"], [8, 4, 1, "", "instance"], [8, 3, 1, "", "log_parameters_to"], [8, 3, 1, "", "lower_bound"], [8, 3, 1, "", "set_model"], [8, 3, 1, "", "set_raw"], [8, 3, 1, "", "sum_up_results"]], "moptipyapps.dynamic_control.objective.FigureOfMeritLE": [[8, 3, 1, "", "sum_up_results"]], "moptipyapps.dynamic_control.ode": [[8, 2, 1, "", "T"], [8, 5, 1, "", "diff_from_ode"], [8, 5, 1, "", "j_from_ode"], [8, 5, 1, "", "multi_run_ode"], [8, 5, 1, "", "run_ode"], [8, 5, 1, "", "t_from_ode"]], "moptipyapps.dynamic_control.results_log": [[8, 2, 1, "", "ResultsLog"]], "moptipyapps.dynamic_control.results_log.ResultsLog": [[8, 3, 1, "", "collector"]], "moptipyapps.dynamic_control.results_plot": [[8, 1, 1, "", "END_COLOR"], [8, 1, 1, "", "END_MARKER"], [8, 2, 1, "", "ResultsPlot"], [8, 1, 1, "", "START_COLOR"], [8, 1, 1, "", "START_MARKER"]], "moptipyapps.dynamic_control.results_plot.ResultsPlot": [[8, 3, 1, "", "collector"]], "moptipyapps.dynamic_control.starting_points": [[8, 5, 1, "", "interesting_point_objective"], [8, 5, 1, "", "interesting_point_transform"], [8, 5, 1, "", "make_interesting_starting_points"]], "moptipyapps.dynamic_control.surrogate_optimizer": [[8, 2, 1, "", "SurrogateOptimizer"]], "moptipyapps.dynamic_control.surrogate_optimizer.SurrogateOptimizer": [[8, 4, 1, "", "fancy_logs"], [8, 4, 1, "", "fes_for_training"], [8, 4, 1, "", "fes_for_warmup"], [8, 4, 1, "", "fes_per_model_run"], [8, 3, 1, "", "initialize"], [8, 3, 1, "", "log_parameters_to"], [8, 4, 1, "", "ms_for_training"], [8, 4, 1, "", "ms_per_model_run"], [8, 3, 1, "", "solve"], [8, 4, 1, "", "system_model"]], "moptipyapps.dynamic_control.system": [[8, 2, 1, "", "System"], [8, 2, 1, "", "T"]], "moptipyapps.dynamic_control.system.System": [[8, 4, 1, "", "control_dims"], [8, 3, 1, "", "describe_system"], [8, 3, 1, "", "describe_system_without_control"], [8, 3, 1, "", "equations"], [8, 4, 1, "", "gamma"], [8, 3, 1, "", "log_parameters_to"], [8, 4, 1, "", "name"], [8, 4, 1, "", "plot_examples"], [8, 3, 1, "", "plot_points"], [8, 4, 1, "", "state_dim_mod"], [8, 4, 1, "", "state_dims"], [8, 4, 1, "", "state_dims_in_j"], [8, 4, 1, "", "test_starting_states"], [8, 4, 1, "", "test_steps"], [8, 4, 1, "", "test_time"], [8, 4, 1, "", "training_starting_states"], [8, 4, 1, "", "training_steps"], [8, 4, 1, "", "training_time"]], "moptipyapps.dynamic_control.system_model": [[8, 2, 1, "", "SystemModel"]], "moptipyapps.dynamic_control.system_model.SystemModel": [[8, 3, 1, "", "log_parameters_to"], [8, 4, 1, "", "model"]], "moptipyapps.dynamic_control.systems": [[10, 0, 0, "-", "lorenz"], [10, 0, 0, "-", "stuart_landau"], [10, 0, 0, "-", "three_coupled_oscillators"]], "moptipyapps.dynamic_control.systems.lorenz": [[10, 1, 1, "", "LORENZ_111"], [10, 1, 1, "", "LORENZ_4"], [10, 5, 1, "", "make_lorenz"]], "moptipyapps.dynamic_control.systems.stuart_landau": [[10, 1, 1, "", "STUART_LANDAU_111"], [10, 1, 1, "", "STUART_LANDAU_4"], [10, 5, 1, "", "make_stuart_landau"]], "moptipyapps.dynamic_control.systems.three_coupled_oscillators": [[10, 1, 1, "", "THREE_COUPLED_OSCILLATORS"], [10, 5, 1, "", "make_3_couple_oscillators"]], "moptipyapps.order1d": [[11, 0, 0, "-", "distances"], [11, 0, 0, "-", "instance"], [11, 0, 0, "-", "space"]], "moptipyapps.order1d.distances": [[11, 5, 1, "", "swap_distance"]], "moptipyapps.order1d.instance": [[11, 2, 1, "", "Instance"], [11, 2, 1, "", "T"]], "moptipyapps.order1d.instance.Instance": [[11, 4, 1, "", "flow_power"], [11, 3, 1, "", "from_sequence_and_distance"], [11, 4, 1, "", "horizon"], [11, 3, 1, "", "log_parameters_to"], [11, 4, 1, "", "tag_titles"], [11, 4, 1, "", "tags"]], "moptipyapps.order1d.space": [[11, 2, 1, "", "OrderingSpace"]], "moptipyapps.order1d.space.OrderingSpace": [[11, 3, 1, "", "from_str"], [11, 4, 1, "", "instance"], [11, 3, 1, "", "to_str"]], "moptipyapps.qap": [[12, 0, 0, "-", "instance"], [12, 0, 0, "-", "objective"], [13, 0, 0, "-", "qaplib"]], "moptipyapps.qap.instance": [[12, 2, 1, "", "Instance"], [12, 5, 1, "", "trivial_bounds"]], "moptipyapps.qap.instance.Instance": [[12, 3, 1, "", "bks"], [12, 4, 1, "", "distances"], [12, 4, 1, "", "flows"], [12, 3, 1, "", "from_qaplib_stream"], [12, 3, 1, "", "from_resource"], [12, 3, 1, "", "list_resources"], [12, 3, 1, "", "log_parameters_to"], [12, 4, 1, "", "lower_bound"], [12, 4, 1, "", "n"], [12, 4, 1, "", "name"], [12, 4, 1, "", "upper_bound"]], "moptipyapps.qap.objective": [[12, 2, 1, "", "QAPObjective"]], "moptipyapps.qap.objective.QAPObjective": [[12, 3, 1, "", "evaluate"], [12, 4, 1, "", "instance"], [12, 3, 1, "", "log_parameters_to"], [12, 3, 1, "", "lower_bound"], [12, 3, 1, "", "upper_bound"]], "moptipyapps.qap.qaplib": [[13, 5, 1, "", "open_resource_stream"]], "moptipyapps.shared": [[3, 1, 1, "", "SCOPE_INSTANCE"], [3, 5, 1, "", "moptipyapps_argparser"], [3, 5, 1, "", "motipyapps_footer_bottom_comments"]], "moptipyapps.tests": [[14, 0, 0, "-", "on_binpacking2d"], [14, 0, 0, "-", "on_tsp"]], "moptipyapps.tests.on_binpacking2d": [[14, 5, 1, "", "binpacking_instances_for_tests"], [14, 5, 1, "", "make_packing_invalid"], [14, 5, 1, "", "make_packing_valid"], [14, 5, 1, "", "validate_algorithm_on_1_2dbinpacking"], [14, 5, 1, "", "validate_algorithm_on_2dbinpacking"], [14, 5, 1, "", "validate_objective_on_1_2dbinpacking"], [14, 5, 1, "", "validate_objective_on_2dbinpacking"], [14, 5, 1, "", "validate_signed_permutation_encoding_on_1_2dbinpacking"], [14, 5, 1, "", "validate_signed_permutation_encoding_on_2dbinpacking"]], "moptipyapps.tests.on_tsp": [[14, 5, 1, "", "make_tour_invalid"], [14, 5, 1, "", "make_tour_valid"], [14, 5, 1, "", "tsp_instances_for_tests"], [14, 5, 1, "", "validate_algorithm_on_1_tsp"], [14, 5, 1, "", "validate_algorithm_on_tsp"], [14, 5, 1, "", "validate_objective_on_1_tsp"], [14, 5, 1, "", "validate_objective_on_tsp"]], "moptipyapps.tsp": [[15, 0, 0, "-", "ea1p1_revn"], [15, 0, 0, "-", "fea1p1_revn"], [15, 0, 0, "-", "instance"], [15, 0, 0, "-", "known_optima"], [15, 0, 0, "-", "tour_length"], [16, 0, 0, "-", "tsplib"]], "moptipyapps.tsp.ea1p1_revn": [[15, 2, 1, "", "TSPEA1p1revn"], [15, 5, 1, "", "rev_if_not_worse"]], "moptipyapps.tsp.ea1p1_revn.TSPEA1p1revn": [[15, 3, 1, "", "log_parameters_to"], [15, 3, 1, "", "solve"]], "moptipyapps.tsp.fea1p1_revn": [[15, 2, 1, "", "TSPFEA1p1revn"], [15, 5, 1, "", "rev_if_h_not_worse"]], "moptipyapps.tsp.fea1p1_revn.TSPFEA1p1revn": [[15, 4, 1, "", "do_log_h"], [15, 4, 1, "", "instance"], [15, 3, 1, "", "log_parameters_to"], [15, 3, 1, "", "solve"]], "moptipyapps.tsp.instance": [[15, 2, 1, "", "Instance"], [15, 5, 1, "", "ncities_from_tsplib_name"]], "moptipyapps.tsp.instance.Instance": [[15, 3, 1, "", "from_file"], [15, 3, 1, "", "from_resource"], [15, 4, 1, "", "is_symmetric"], [15, 3, 1, "", "list_resources"], [15, 3, 1, "", "log_parameters_to"], [15, 4, 1, "", "n_cities"], [15, 4, 1, "", "name"], [15, 3, 1, "", "to_stream"], [15, 4, 1, "", "tour_length_lower_bound"], [15, 4, 1, "", "tour_length_upper_bound"]], "moptipyapps.tsp.known_optima": [[15, 5, 1, "", "list_resource_tours"], [15, 5, 1, "", "opt_tour_from_file"], [15, 5, 1, "", "opt_tour_from_resource"]], "moptipyapps.tsp.tour_length": [[15, 2, 1, "", "TourLength"], [15, 5, 1, "", "tour_length"]], "moptipyapps.tsp.tour_length.TourLength": [[15, 3, 1, "", "evaluate"], [15, 4, 1, "", "instance"], [15, 3, 1, "", "log_parameters_to"], [15, 3, 1, "", "lower_bound"], [15, 3, 1, "", "upper_bound"]], "moptipyapps.tsp.tsplib": [[16, 5, 1, "", "open_resource_stream"]], "moptipyapps.ttp": [[17, 0, 0, "-", "errors"], [17, 0, 0, "-", "game_encoding"], [17, 0, 0, "-", "game_plan"], [17, 0, 0, "-", "game_plan_space"], [17, 0, 0, "-", "instance"], [17, 0, 0, "-", "plan_length"], [18, 0, 0, "-", "robinx"]], "moptipyapps.ttp.errors": [[17, 2, 1, "", "Errors"], [17, 5, 1, "", "count_errors"]], "moptipyapps.ttp.errors.Errors": [[17, 3, 1, "", "evaluate"], [17, 4, 1, "", "instance"], [17, 3, 1, "", "is_always_integer"], [17, 3, 1, "", "lower_bound"], [17, 3, 1, "", "upper_bound"]], "moptipyapps.ttp.game_encoding": [[17, 2, 1, "", "GameEncoding"], [17, 5, 1, "", "map_games"], [17, 5, 1, "", "search_space_for_n_and_rounds"]], "moptipyapps.ttp.game_encoding.GameEncoding": [[17, 4, 1, "", "instance"], [17, 3, 1, "", "search_space"]], "moptipyapps.ttp.game_plan": [[17, 2, 1, "", "GamePlan"]], "moptipyapps.ttp.game_plan.GamePlan": [[17, 4, 1, "", "instance"]], "moptipyapps.ttp.game_plan_space": [[17, 2, 1, "", "GamePlanSpace"]], "moptipyapps.ttp.game_plan_space.GamePlanSpace": [[17, 3, 1, "", "create"], [17, 3, 1, "", "from_str"], [17, 4, 1, "", "instance"], [17, 3, 1, "", "is_equal"], [17, 3, 1, "", "log_parameters_to"], [17, 3, 1, "", "n_points"], [17, 3, 1, "", "validate"]], "moptipyapps.ttp.instance": [[17, 2, 1, "", "Instance"]], "moptipyapps.ttp.instance.Instance": [[17, 4, 1, "", "away_streak_max"], [17, 4, 1, "", "away_streak_min"], [17, 3, 1, "", "from_file"], [17, 3, 1, "", "from_resource"], [17, 4, 1, "", "game_plan_dtype"], [17, 3, 1, "", "get_optimal_plan_length_bounds"], [17, 4, 1, "", "home_streak_max"], [17, 4, 1, "", "home_streak_min"], [17, 3, 1, "", "list_resources"], [17, 3, 1, "", "log_parameters_to"], [17, 4, 1, "", "rounds"], [17, 4, 1, "", "separation_max"], [17, 4, 1, "", "separation_min"], [17, 4, 1, "", "teams"]], "moptipyapps.ttp.plan_length": [[17, 2, 1, "", "GamePlanLength"], [17, 5, 1, "", "game_plan_length"]], "moptipyapps.ttp.plan_length.GamePlanLength": [[17, 4, 1, "", "bye_penalty"], [17, 3, 1, "", "evaluate"], [17, 4, 1, "", "instance"], [17, 3, 1, "", "is_always_integer"], [17, 3, 1, "", "log_parameters_to"], [17, 3, 1, "", "lower_bound"], [17, 3, 1, "", "upper_bound"]], "moptipyapps.ttp.robinx": [[18, 5, 1, "", "open_resource_stream"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "data", "Python data"], "2": ["py", "class", "Python class"], "3": ["py", "method", "Python method"], "4": ["py", "attribute", "Python attribute"], "5": ["py", "function", "Python function"]}, "objtypes": {"0": "py:module", "1": "py:data", "2": "py:class", "3": "py:method", "4": "py:attribute", "5": "py:function"}, "terms": {"": [0, 1, 3, 4, 5, 6, 7, 8, 9, 11, 12, 15, 17], "0": [0, 1, 4, 6, 7, 8, 9, 10, 11, 12, 15, 17], "000": 8, "0000000000": 8, "001": [0, 1, 11], "00367": 10, "00367v1": 10, "00437": [4, 5], "00445931": 8, "005": [4, 12], "01": 17, "012": [0, 1, 11], "01717796": 8, "01808": 4, "021": 4, "03": 17, "030": 17, "0303": 12, "032": 12, "03484": 8, "037": 12, "04": [12, 17], "05": [12, 17], "061": 17, "06765771": 8, "07757": 8, "08": 4, "084": [8, 9, 10], "09": 12, "0x7f2d51cf11c0": 15, "0x7f2d51e91b60": 14, "0x7f2d51e93a00": 14, "0x7f2d7fd1d800": 4, "0x7f2d90720770": 4, "0x7f2d911a4900": 4, "0x7f2d9126f2e0": 4, "0x7f2d9135e7a0": 4, "0x7f2d913656c0": 4, "0x7f2d913daa20": 4, "0x7f2d913daac0": 4, "0x7f2d913dac00": 4, "0x7f2d913daca0": 4, "0x7f2d913dad40": 4, "0x7f2d913dade0": 4, "0x7f2d913daf20": 4, "0x7f2d913dafc0": 4, "0x7f2d91722b60": 4, "0x7f2d9174d580": 4, "1": [4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 17], "10": [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17], "100": [4, 6, 7, 8, 11, 14, 15], "1000": 8, "10000": [6, 8], "100000": 6, "1000000": [4, 6], "1001": 4, "10022296": 15, "10061706": 6, "10064146": 6, "10065000": 6, "1006529012972": 15, "1007": [4, 12, 15, 17], "1008293323270": [12, 13], "1016": [4, 5, 12, 17], "1017": 15, "1019": 8, "1020": 8, "1023": [12, 13, 15], "1035": 7, "10409396852733332453861621760000": 4, "10482": 8, "105": 12, "10500": 7, "1052": 8, "1057": 4, "1058": 8, "10677249": 8, "1093": 4, "1098": 6, "10f": 8, "11": [4, 8, 12, 15, 17], "110": [0, 1, 8], "1101": 6, "1109": 15, "111": [10, 12, 15], "112": [4, 5], "1120": 4, "11245": 4, "113": 15, "11484713": 8, "1158": 4, "1160": 6, "118": 17, "12": [0, 1, 4, 6, 7, 8, 12, 15, 17], "1208": 6, "1215": 6, "1216": 6, "1219": 6, "122": 6, "1220": [4, 6], "128": 8, "1287": [15, 16], "129": 15, "12993": 15, "13": [4, 6, 7, 12, 15, 17], "134": [12, 15], "1389": 4, "14": [4, 8, 12, 15, 17], "1420": 12, "14285714285714": 8, "1428571429": 8, "144": [7, 8], "145": 17, "1483": 15, "15": [4, 6, 7, 12, 15, 17], "15000": 7, "15388": 12, "154": 17, "156": 4, "1560": 15, "16": [4, 6, 8, 11, 12, 17], "160": [12, 17], "162": 17, "163": [0, 1, 4, 5, 6, 7], "1649": 6, "1680": 4, "17": [6, 12, 15, 17], "170": 15, "1705": 10, "171": 6, "1713": 12, "173": [12, 15], "174": 15, "175": 15, "176": 12, "179": 17, "18": [4, 8, 12, 15, 17], "1809": 12, "18515": 8, "1852": 4, "187": 12, "19": [6, 12, 15, 17], "1932": 17, "1938": 17, "196": 4, "1982": 4, "1985": 15, "1987": 4, "1991": [12, 15, 16], "1993": 12, "1994": 12, "1995": [12, 15, 16], "1997": [12, 13], "1998": 12, "1999": [4, 5, 15], "1e100": 8, "1e50": 8, "2": [4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 17], "20": [4, 6, 7, 12], "2000": 15, "2001": 17, "2002": 15, "2003": 12, "2004": 17, "2005": [12, 17], "2006": 17, "2007": [0, 1, 12, 15, 17], "2008": 12, "2009": 4, "200922": 8, "2010": 4, "2011": [4, 12, 13], "20132119": 8, "2014": 15, "2016": 4, "2017": 10, "2018": [10, 12, 13], "2022": [4, 8, 9, 10, 15], "202208220937": [8, 9, 10], "2023": [0, 1, 8, 9, 10, 12, 13], "2024": [12, 15, 17], "209": 4, "21": [7, 12, 13], "21311": 4, "216": 11, "22": [12, 15], "220270875": 17, "22068": 15, "2217": [4, 5], "224": 8, "224416": 12, "2291": 15, "23": [4, 12], "2315": 4, "2326101": 15, "23423923": 8, "24": [4, 6, 12, 17], "24355": [8, 9, 10], "244": [6, 8], "24423": 10, "248": 15, "25": [4, 7, 11, 12, 17], "2500": 7, "256": 14, "2579": 15, "26": [12, 17], "2600": 7, "26154": 8, "2698": 6, "27": [4, 6, 11, 12], "2700": 7, "2730": 6, "2748": 6, "2750": [4, 6], "27921153": 8, "28": 12, "280": 15, "28108707": 8, "286": 4, "2887": 8, "29": [0, 1, 12], "2992": 12, "2d": [0, 1, 4, 6, 14], "2dpacklib": [0, 1, 4], "2n": 17, "2nd": [0, 1, 11], "3": [4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17], "30": [7, 10, 12, 17], "300": 7, "3000": 10, "30774895": 8, "31": [7, 12, 17], "315092303249095": 4, "32": 12, "324": 8, "33": 8, "33029": 8, "330799": 15, "335": 15, "33630621": 8, "337": 15, "35": 11, "353": 4, "357": 4, "36": [6, 11], "360": 15, "366": 6, "367": 15, "3672": 12, "37": 4, "3715414": 8, "376": [15, 16], "3774": 15, "3778740795710009": 6, "3781": 15, "3796": 12, "38": [4, 6, 8, 17], "384": [4, 15, 16], "386": 4, "39": 6, "39003072": 8, "39064": 8, "391": [12, 13], "3_19": 4, "3f": 8, "3oscil": 10, "4": [4, 5, 6, 7, 8, 10, 11, 12, 15, 16, 17], "40": [4, 7, 15], "403": [12, 13], "403374": 8, "407810": 8, "41234557": 8, "413": [4, 5], "41881729": 8, "420": [4, 5], "4209649817": 8, "423": 4, "42863": 17, "429": 4, "43": 12, "4314431443": 8, "433": 4, "4364436444": 8, "441": 6, "443": 12, "44328": 8, "45": [6, 8], "455": 12, "45578": 17, "4562": 12, "4571309653": 8, "46": 17, "4613": 12, "463": 4, "47": 17, "471": [4, 15], "480": 4, "486": 4, "49": 3, "49071198": 8, "497": 12, "5": [4, 5, 6, 7, 8, 11, 12, 15, 17], "50": [4, 7, 8], "500": 4, "5000": 8, "50305736": 8, "50474": 12, "505": 10, "509": 7, "5100": 7, "51392": 8, "514": 12, "516": 4, "52": 15, "52310": 8, "525": 7, "534": [7, 10], "540": 17, "5426670": 12, "545914": 4, "55": 12, "557": 4, "56": 4, "578": 12, "58": 6, "580": 17, "584": 17, "5f": 8, "5th": 11, "6": [4, 6, 8, 10, 11, 12, 15, 17], "60": [4, 7], "6000": 7, "615": 7, "616": 8, "618033988749895": 9, "62": 6, "64": [8, 11], "642": 4, "65406": 15, "657": 12, "660": 6, "6600": 7, "66154475": 8, "66567": 8, "6681531": 8, "67": 17, "6864377172744689378196133203444067624537070830997366604446306636401": 17, "69": 4, "690": [8, 12], "691": 15, "7": [4, 6, 7, 8, 11, 12, 15, 17], "70": [4, 8, 10], "706": 4, "71067811865474": 8, "7106781187": 8, "7162129779": 8, "72": 8, "7305688": 6, "74": 4, "74535599": 8, "75": [4, 8], "750": 8, "75627": 8, "75724629": 8, "75766315": 8, "75958357": 8, "7600": 7, "76928033": 8, "772": 4, "775": 17, "7750": 7, "77767": 8, "787": 17, "7900": 7, "79766443076872509863361": 17, "7_43": 17, "8": [4, 8, 11, 12, 15, 17], "80": 4, "806": 4, "80665": 8, "8107197148": 8, "830": 15, "8300": 7, "84": 7, "840": 4, "8411064": 8, "84621757": 8, "84901441": 8, "851": 17, "86": 12, "86153": 8, "87": 12, "8750": 7, "8776461858988774": 6, "878": 6, "8781459580052053": 6, "882": 8, "88214": 8, "890": 8, "8th": 4, "9": [4, 6, 7, 8, 11, 12, 15, 17], "90": [0, 1, 5, 6], "900": 7, "902": 8, "90413": 15, "92": 15, "9223372036854775807": 4, "924": 8, "926": 4, "93": 6, "94": 7, "95": 12, "9547": 15, "961": [7, 8], "97": [4, 5, 8, 12], "976": 6, "978": [4, 12, 17], "9781612085104": 4, "98": [6, 8], "986": 4, "9866528870384179": 6, "9876395399254564": 6, "991": 4, "9910948": 6, "9995959203471327": 6, "9_27": 12, "A": [0, 1, 4, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17], "And": [0, 1, 5, 7, 8, 11], "As": [6, 9, 15, 17], "At": [6, 11], "Be": 15, "But": [0, 1, 5, 6, 7, 8, 9, 11, 15], "By": 17, "FOR": [0, 1], "For": [0, 1, 5, 7, 8, 9, 11, 15, 17], "If": [0, 1, 4, 5, 6, 7, 8, 9, 11, 15, 17], "In": [0, 1, 4, 5, 6, 7, 8, 9, 11, 12, 15, 17], "It": [0, 1, 4, 5, 6, 7, 8, 11, 15, 17], "NOT": 11, "No": 17, "OR": 4, "Of": [0, 1, 6, 8, 11, 15, 17], "On": [0, 1, 15], "One": [4, 11, 17], "Or": [0, 1], "Such": [0, 1, 4, 8, 11, 15, 17], "That": [0, 1, 5, 8, 11], "The": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18], "Then": [0, 1, 5, 6, 8, 11, 15, 17], "There": [4, 10, 11, 17], "These": [0, 1, 6, 8, 9, 15, 17], "To": [0, 1, 6, 8], "With": [4, 6, 7, 8], "_": [3, 15], "__file__": [3, 15, 17], "__getitem__": 15, "__lb_geometr": 4, "__normalize_2dpacklib_inst_nam": 4, "_bpcmae": 8, "_decod": 4, "_dist": 11, "_dist2": 11, "_from_stream": 15, "_tag": 11, "a01": 4, "a02": 4, "a03": 4, "a04": [4, 6], "a04n": 6, "a05": 4, "a06": 4, "a07": 4, "a08": 4, "a09": 4, "a1": 6, "a10": 4, "a11": 4, "a12": 4, "a13": 4, "a14": 4, "a15": 4, "a16": 4, "a17": 4, "a18": 4, "a19": 4, "a20": 4, "a21": 4, "a22": 4, "a23": 4, "a24": 4, "a25": 4, "a26": 4, "a27": 4, "a28": 4, "a280": 15, "a29": 4, "a30": 4, "a31": 4, "a32": 4, "a33": 4, "a34": 4, "a35": 4, "a36": 4, "a37": 4, "a38": 4, "a39": 4, "a40": 4, "a41": 4, "a42": 4, "a43": 4, "a_larg": [3, 4], "a_m": [3, 4], "a_smal": [3, 4], "ab": [9, 10, 11], "abl": [8, 17], "about": [0, 1, 8, 11, 15, 17], "abov": [0, 1, 3, 5, 6, 7, 8, 9, 11], "abraham": 15, "abreu": 12, "abroad": 17, "absenc": [0, 1], "absolut": 6, "abstractcontextmanag": 8, "academ": 15, "accept": 15, "access": 12, "accommod": 7, "accord": [0, 1, 5, 11], "accordingli": 8, "account": 15, "accur": [8, 9], "accuraci": 8, "achiev": [0, 1, 8], "activ": [0, 1, 9], "actual": [0, 1, 5, 6, 7, 8, 9, 11, 15, 16, 18], "ad": [0, 1, 4, 6, 7, 8, 10, 15, 17], "adapt": 12, "add": [0, 1, 4, 6, 7, 8, 17], "addit": [0, 1, 3, 4, 6, 9, 11], "addition": [0, 1, 4, 6, 12, 15, 17], "address": [0, 1], "adher": 17, "administr": [17, 18], "advic": 8, "advis": 15, "affect": 17, "after": [4, 6, 8, 11, 17], "again": [0, 1, 5, 6, 7, 8, 10, 15, 17], "against": [0, 1, 17], "aggreg": [0, 1, 4], "ah": 15, "ai": 4, "aida": 12, "aim": 12, "alert": [0, 1], "alexand": 15, "alfonsa": 12, "algo_from_pr": 4, "algo_select": 4, "algo_sort_kei": 4, "algorithm": [0, 1, 3, 4, 5, 6, 8, 9, 12, 14, 15, 17], "ali535": 15, "alia": [8, 11], "all": [0, 1, 4, 5, 6, 7, 8, 9, 11, 12, 15, 17], "allevi": [5, 8], "alloc": 8, "allow": [4, 6, 7, 8, 9, 10, 15, 17], "almost": [0, 1, 4, 7], "alon": 5, "along": [0, 1], "alphabet": [0, 1], "alreadi": [5, 8, 11, 15, 17], "also": [0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 15, 17], "altern": 8, "alv": 4, "alwai": [0, 1, 5, 6, 7, 8, 9, 11, 17], "american": 12, "among": [0, 1, 9], "amongst": 7, "amount": [0, 1, 6], "an": [0, 1, 3, 4, 5, 6, 7, 8, 9, 11, 12, 14, 15, 17, 18], "analys": [0, 1], "analyt": 4, "analyz": [0, 1], "anchor": 9, "andrea": 12, "angewandt": [15, 16], "angl": 8, "anhui": [0, 1, 4, 5, 6, 7, 12, 15, 17], "ani": [0, 1, 3, 4, 5, 6, 7, 8, 9, 11, 12, 15, 17], "anjo": [0, 1], "ann": [3, 8], "anneal": 12, "annoi": 5, "annot": [0, 1], "anoth": [0, 1, 4, 5, 6, 7, 8, 11, 17], "ant": 12, "anti": [0, 1], "anupam": 15, "anymor": [5, 7, 8], "anyth": [6, 11, 15, 16, 18], "anywai": [0, 1, 4, 8, 11], "aom": 10, "ap": 3, "apart": 11, "api": [0, 1, 4, 17], "appear": [11, 17], "append": [4, 15], "append_almost_squares_str": [3, 4], "appleg": 15, "appli": [0, 1, 4, 5, 6, 7, 8, 12, 15, 17], "applic": [3, 4, 6, 12], "approach": [4, 8], "appropri": 8, "approxim": [4, 6, 15], "apr": 10, "april": 15, "ar": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 17], "arang": 11, "arbitrari": 9, "arbitrarili": 6, "arc": 4, "architectur": 9, "archiv": [4, 10], "arctan": 9, "area": [4, 6, 7], "area_of_items_in_last_bin": 7, "arg": 9, "argpars": 3, "argument": [3, 17], "argumentpars": 3, "argwher": 8, "arithmet": 8, "around": [0, 1, 8, 9, 10, 16, 18], "arrai": [4, 5, 6, 7, 8, 11, 12, 17], "arrang": [0, 1, 11], "array2str": 15, "arriv": [6, 8, 11], "artifici": [0, 1, 4, 5, 6, 7, 8, 9, 12, 15, 17], "arxiv": 10, "asqa": [3, 4], "asqas03": 4, "asqas08": 4, "asqas20": 4, "asqas34": 4, "assess": 6, "assign": [4, 8, 11, 12, 13, 15, 17], "associ": 8, "assum": [11, 15], "asymmetr": [14, 15, 17], "atl": 17, "atop": [0, 1], "atsp": 15, "att48": 15, "att532": 15, "attempt": 8, "attribut": 4, "august": [4, 15, 17], "author": [0, 1], "autoflak": [0, 1], "automat": [0, 1, 5, 9, 15], "avail": [0, 1, 4, 8, 12, 15, 17], "averag": [4, 8, 11], "awai": [0, 1, 6, 8, 11, 15, 17], "awar": 15, "away_idi": 17, "away_idx": 17, "away_streak_max": [3, 17], "away_streak_min": [3, 17], "awaystreakmax": 17, "awaystreakmin": 17, "awesom": 9, "axi": [0, 1, 11], "a\u00b2": 9, "a\u00b2b": 9, "a\u00b3": 9, "b": [0, 1, 4, 7, 8, 9, 11, 15, 17], "b1": 11, "b101971": 15, "b1982prpaha": 4, "b2": 11, "b4": 11, "back": [0, 1, 4, 6, 8, 9, 15, 17], "bad": 8, "baidu": 15, "baidumap": 15, "bandit": [0, 1], "base": [0, 1, 4, 5, 6, 7, 8, 9, 11, 12, 14, 15, 17], "base_dir": [4, 6, 8], "base_nam": 8, "base_setup": [3, 4, 8], "basi": 17, "basic": [0, 1, 3, 4, 5, 6, 8, 9, 11, 15, 17], "bayg29": 15, "bays29": 15, "bdc2015rhhaftoono2bpp": 4, "becaus": [0, 1, 5, 6, 7, 8, 11, 15, 17], "becom": [5, 7, 8, 9, 12], "been": [0, 1, 3, 4, 5, 6, 7, 12, 15, 17], "befor": [0, 1, 6, 8, 11, 15, 16, 17], "begin": [0, 1, 3, 5, 6, 8, 11, 17], "begin_i": [4, 15, 17], "behav": 8, "behavior": [8, 9], "behind": 7, "beij": 15, "being": [7, 8], "belgium": [17, 18], "belong": 4, "below": [0, 1, 7, 17], "benchmark": [0, 1, 4, 6, 12, 13, 15, 17, 18], "benefici": [3, 8], "beng": 4, "beng01": 4, "beng02": 4, "beng03": 4, "beng04": 4, "beng05": 4, "beng06": 4, "beng07": 4, "beng08": 4, "beng09": 4, "beng10": 4, "beng_1_8": [3, 4], "beng_9_10": [3, 4], "bengt": 4, "bengtsson": 4, "berg": [4, 15], "berkei": 4, "berlin": [4, 17], "berlin52": 15, "bernd": [8, 9, 10], "besid": [0, 1], "best": [0, 1, 4, 7, 8, 12], "better": [0, 1, 5, 7, 8, 15], "between": [0, 1, 7, 8, 11, 12, 15, 17], "bhulai": 4, "bi": 8, "bibtex": 4, "bier127": 15, "big": [0, 1, 4, 5, 6, 7, 11, 12, 15, 17], "bin": [3, 4, 5, 6, 7, 14, 17], "bin_area": 7, "bin_bound": [3, 4], "bin_count": [3, 4], "bin_count_and_empti": [3, 4], "bin_count_and_last_empti": [3, 4], "bin_count_and_last_skylin": [3, 4], "bin_count_and_last_smal": [3, 4], "bin_count_and_lowest_skylin": [3, 4], "bin_count_and_smal": [3, 4], "bin_count_nam": [4, 7], "bin_height": [3, 4, 6, 7], "bin_width": [3, 4, 6, 7], "binari": 6, "bincount": [4, 7], "bincountandempti": [4, 7], "bincountandlastempti": [4, 7], "bincountandlastskylin": [4, 7], "bincountandlastsmal": [4, 7], "bincountandlowestskylin": [4, 7], "bincountandsmal": [4, 7], "binheight": 4, "binpacking2d": [0, 1, 2, 3], "binpacking_instances_for_test": [3, 14], "binpacklib2d_fil": 4, "binwidth": 4, "bipop": [6, 8], "bipopcma": 8, "bit": [5, 6, 8, 11], "bixbi": 15, "bk": [2, 3, 12], "bl": [4, 5], "bla": [3, 11], "black": [0, 1, 9, 14, 17], "blade": 8, "blue": 8, "blueprint": [8, 9, 17], "boaventura": 12, "book": [8, 10], "bool": [4, 6, 7, 8, 14, 15, 17], "border": 7, "bor\u00e9": 10, "both": [4, 8, 11, 17], "bother": 11, "bottem": 5, "bottom": [0, 1, 4, 5], "bound": [0, 1, 4, 6, 7, 8, 11, 12, 15, 17], "box": [0, 1, 9, 14, 17], "bpp": [4, 6], "br17": 15, "bra24": 17, "braam": 4, "braunschweig": [8, 9, 10], "brazil58": 15, "break": 17, "brg180": 15, "bring": [0, 1, 8], "brkga": 4, "brkga_bpp_2r": [3, 4], "brkga_bpp_anb": [3, 4], "brkgabppranb": 4, "brkgabpprtr": 4, "bs2013st2bppbmoahea": 4, "budget": 8, "bug": [0, 1], "bugbear": [0, 1], "build": [0, 1, 8, 9], "built": 15, "bulck": [0, 1, 17, 18], "bur26a": 12, "burkard": [0, 1, 12, 13], "busi": [12, 17, 18], "bw1987tdfbpa": 4, "bye": 17, "bye_penalti": [3, 17], "b\u00b2": 9, "b\u00b2a": 9, "b\u00b3": 9, "b\u00f6lte": 12, "c": [0, 1, 7, 8, 9, 15, 17], "ca": [0, 1, 12, 13], "cach": [4, 9], "calcul": [4, 15], "call": [0, 1, 3, 4, 6, 7, 8, 15, 17], "callabl": [4, 6, 8, 9, 11, 14, 15, 17], "can": [0, 1, 3, 4, 6, 7, 8, 9, 11, 12, 14, 15, 16, 17, 18], "candid": [12, 15, 17], "cannot": [0, 1, 5, 7, 8, 11, 15, 17], "canon": 4, "cao": 17, "car": 15, "care": [0, 1, 11, 17], "carvalho": 4, "case": [0, 1, 4, 5, 6, 7, 8, 9, 11, 17], "cast": 11, "caus": [0, 1, 17], "cc": [0, 1], "celso": 17, "center": [0, 1, 11], "certain": [5, 7, 9], "cgrs2020pacatsmtoosft2bpp": 4, "ch130": 15, "ch150": 15, "chanc": [5, 7], "chang": [0, 1, 6, 8, 15], "changsha": 15, "characterist": 6, "charl": 12, "chart": [0, 1, 4, 5], "cheap": [8, 9], "check": [0, 1, 4, 5, 6, 14, 15, 17], "chee": 15, "chen": [12, 15], "chichest": 15, "china": [0, 1, 4, 5, 6, 7, 8, 9, 10, 12, 15, 17], "chines": 15, "chiong": 15, "choic": 9, "chongq": 15, "choos": [6, 11], "chose": 11, "chosen": [6, 8], "chr25a": 12, "chunyan": 15, "chv\u00e1tal": 15, "cindi": 15, "circ4": 17, "circ6": 17, "circ8": 17, "circl": [0, 1, 10, 11], "circular": [0, 1], "citi": [0, 1, 15, 17], "cl01_020_01": 4, "cl01_020_02": 4, "cl01_020_03": 4, "cl01_020_04": 4, "cl01_020_05": 4, "cl01_020_06": 4, "cl01_020_07": 4, "cl01_020_08": 4, "cl01_020_09": 4, "cl01_020_10": 4, "cl01_040_01": 4, "cl01_040_02": 4, "cl01_040_03": 4, "cl01_040_04": 4, "cl01_040_05": 4, "cl01_040_06": 4, "cl01_040_07": 4, "cl01_040_08": 4, "cl01_040_09": 4, "cl01_040_10": 4, "cl01_060_01": 4, "cl01_060_02": 4, "cl01_060_03": 4, "cl01_060_04": 4, "cl01_060_05": 4, "cl01_060_06": 4, "cl01_060_07": 4, "cl01_060_08": 4, "cl01_060_09": 4, "cl01_060_10": 4, "cl01_080_01": 4, "cl01_080_02": 4, "cl01_080_03": 4, "cl01_080_04": 4, "cl01_080_05": 4, "cl01_080_06": 4, "cl01_080_07": 4, "cl01_080_08": 4, "cl01_080_09": 4, "cl01_080_10": 4, "cl01_100_01": 4, "cl01_100_02": 4, "cl01_100_03": 4, "cl01_100_04": 4, "cl01_100_05": 4, "cl01_100_06": 4, "cl01_100_07": 4, "cl01_100_08": 4, "cl01_100_09": 4, "cl01_100_10": 4, "cl02_020_01": 4, "cl02_020_02": 4, "cl02_020_03": 4, "cl02_020_04": 4, "cl02_020_05": 4, "cl02_020_06": 4, "cl02_020_07": 4, "cl02_020_08": 4, "cl02_020_09": 4, "cl02_020_10": 4, "cl02_040_01": 4, "cl02_040_02": 4, "cl02_040_03": 4, "cl02_040_04": 4, "cl02_040_05": 4, "cl02_040_06": 4, "cl02_040_07": 4, "cl02_040_08": 4, "cl02_040_09": 4, "cl02_040_10": 4, "cl02_060_01": 4, "cl02_060_02": 4, "cl02_060_03": 4, "cl02_060_04": 4, "cl02_060_05": 4, "cl02_060_06": 4, "cl02_060_07": 4, "cl02_060_08": 4, "cl02_060_09": 4, "cl02_060_10": 4, "cl02_080_01": 4, "cl02_080_02": 4, "cl02_080_03": 4, "cl02_080_04": 4, "cl02_080_05": 4, "cl02_080_06": 4, "cl02_080_07": 4, "cl02_080_08": 4, "cl02_080_09": 4, "cl02_080_10": 4, "cl02_100_01": 4, "cl02_100_02": 4, "cl02_100_03": 4, "cl02_100_04": 4, "cl02_100_05": 4, "cl02_100_06": 4, "cl02_100_07": 4, "cl02_100_08": 4, "cl02_100_09": 4, "cl02_100_10": 4, "cl03_020_01": 4, "cl03_020_02": 4, "cl03_020_03": 4, "cl03_020_04": 4, "cl03_020_05": 4, "cl03_020_06": 4, "cl03_020_07": 4, "cl03_020_08": 4, "cl03_020_09": 4, "cl03_020_10": 4, "cl03_040_01": 4, "cl03_040_02": 4, "cl03_040_03": 4, "cl03_040_04": 4, "cl03_040_05": 4, "cl03_040_06": 4, "cl03_040_07": 4, "cl03_040_08": 4, "cl03_040_09": 4, "cl03_040_10": 4, "cl03_060_01": 4, "cl03_060_02": 4, "cl03_060_03": 4, "cl03_060_04": 4, "cl03_060_05": 4, "cl03_060_06": 4, "cl03_060_07": 4, "cl03_060_08": 4, "cl03_060_09": 4, "cl03_060_10": 4, "cl03_080_01": 4, "cl03_080_02": 4, "cl03_080_03": 4, "cl03_080_04": 4, "cl03_080_05": 4, "cl03_080_06": 4, "cl03_080_07": 4, "cl03_080_08": 4, "cl03_080_09": 4, "cl03_080_10": 4, "cl03_100_01": 4, "cl03_100_02": 4, "cl03_100_03": 4, "cl03_100_04": 4, "cl03_100_05": 4, "cl03_100_06": 4, "cl03_100_07": 4, "cl03_100_08": 4, "cl03_100_09": 4, "cl03_100_10": 4, "cl04_020_01": 4, "cl04_020_01n": 6, "cl04_020_02": 4, "cl04_020_03": 4, "cl04_020_04": 4, "cl04_020_05": 4, "cl04_020_06": 4, "cl04_020_07": 4, "cl04_020_08": 4, "cl04_020_09": 4, "cl04_020_10": 4, "cl04_040_01": 4, "cl04_040_02": 4, "cl04_040_03": 4, "cl04_040_04": 4, "cl04_040_05": 4, "cl04_040_06": 4, "cl04_040_07": 4, "cl04_040_08": 4, "cl04_040_09": 4, "cl04_040_10": 4, "cl04_060_01": 4, "cl04_060_02": 4, "cl04_060_03": 4, "cl04_060_04": 4, "cl04_060_05": 4, "cl04_060_06": 4, "cl04_060_07": 4, "cl04_060_08": 4, "cl04_060_09": 4, "cl04_060_10": 4, "cl04_080_01": 4, "cl04_080_02": 4, "cl04_080_03": 4, "cl04_080_04": 4, "cl04_080_05": 4, "cl04_080_06": 4, "cl04_080_07": 4, "cl04_080_08": 4, "cl04_080_09": 4, "cl04_080_10": 4, "cl04_100_01": 4, "cl04_100_02": 4, "cl04_100_03": 4, "cl04_100_04": 4, "cl04_100_05": 4, "cl04_100_06": 4, "cl04_100_07": 4, "cl04_100_08": 4, "cl04_100_09": 4, "cl04_100_10": 4, "cl05_020_01": 4, "cl05_020_02": 4, "cl05_020_03": 4, "cl05_020_04": 4, "cl05_020_05": 4, "cl05_020_06": 4, "cl05_020_07": 4, "cl05_020_08": 4, "cl05_020_09": 4, "cl05_020_10": 4, "cl05_040_01": 4, "cl05_040_02": 4, "cl05_040_03": 4, "cl05_040_04": 4, "cl05_040_05": 4, "cl05_040_06": 4, "cl05_040_07": 4, "cl05_040_08": 4, "cl05_040_09": 4, "cl05_040_10": 4, "cl05_060_01": 4, "cl05_060_02": 4, "cl05_060_03": 4, "cl05_060_04": 4, "cl05_060_05": 4, "cl05_060_06": 4, "cl05_060_07": 4, "cl05_060_08": 4, "cl05_060_09": 4, "cl05_060_10": 4, "cl05_080_01": 4, "cl05_080_02": 4, "cl05_080_03": 4, "cl05_080_04": 4, "cl05_080_05": 4, "cl05_080_06": 4, "cl05_080_07": 4, "cl05_080_08": 4, "cl05_080_09": 4, "cl05_080_10": 4, "cl05_100_01": 4, "cl05_100_02": 4, "cl05_100_03": 4, "cl05_100_04": 4, "cl05_100_05": 4, "cl05_100_06": 4, "cl05_100_07": 4, "cl05_100_08": 4, "cl05_100_09": 4, "cl05_100_10": 4, "cl06_020_01": 4, "cl06_020_02": 4, "cl06_020_03": 4, "cl06_020_04": 4, "cl06_020_05": 4, "cl06_020_06": 4, "cl06_020_07": 4, "cl06_020_08": 4, "cl06_020_09": 4, "cl06_020_10": 4, "cl06_040_01": 4, "cl06_040_02": 4, "cl06_040_03": 4, "cl06_040_04": 4, "cl06_040_05": 4, "cl06_040_06": 4, "cl06_040_07": 4, "cl06_040_08": 4, "cl06_040_09": 4, "cl06_040_10": 4, "cl06_060_01": 4, "cl06_060_02": 4, "cl06_060_03": 4, "cl06_060_04": 4, "cl06_060_05": 4, "cl06_060_06": 4, "cl06_060_07": 4, "cl06_060_08": 4, "cl06_060_09": 4, "cl06_060_10": 4, "cl06_080_01": 4, "cl06_080_02": 4, "cl06_080_03": 4, "cl06_080_04": 4, "cl06_080_05": 4, "cl06_080_06": 4, "cl06_080_07": 4, "cl06_080_08": 4, "cl06_080_09": 4, "cl06_080_10": 4, "cl06_100_01": 4, "cl06_100_02": 4, "cl06_100_03": 4, "cl06_100_04": 4, "cl06_100_05": 4, "cl06_100_06": 4, "cl06_100_07": 4, "cl06_100_08": 4, "cl06_100_09": 4, "cl06_100_10": 4, "cl07_020_01": 4, "cl07_020_02": 4, "cl07_020_03": 4, "cl07_020_04": 4, "cl07_020_05": 4, "cl07_020_06": 4, "cl07_020_07": 4, "cl07_020_08": 4, "cl07_020_09": 4, "cl07_020_10": 4, "cl07_040_01": 4, "cl07_040_02": 4, "cl07_040_03": 4, "cl07_040_04": 4, "cl07_040_05": 4, "cl07_040_06": 4, "cl07_040_07": 4, "cl07_040_08": 4, "cl07_040_09": 4, "cl07_040_10": 4, "cl07_060_01": 4, "cl07_060_02": 4, "cl07_060_03": 4, "cl07_060_04": 4, "cl07_060_05": 4, "cl07_060_06": 4, "cl07_060_07": 4, "cl07_060_08": 4, "cl07_060_09": 4, "cl07_060_10": 4, "cl07_080_01": 4, "cl07_080_02": 4, "cl07_080_03": 4, "cl07_080_04": 4, "cl07_080_05": 4, "cl07_080_06": 4, "cl07_080_07": 4, "cl07_080_08": 4, "cl07_080_09": 4, "cl07_080_10": 4, "cl07_100_01": 4, "cl07_100_02": 4, "cl07_100_03": 4, "cl07_100_04": 4, "cl07_100_05": 4, "cl07_100_06": 4, "cl07_100_07": 4, "cl07_100_08": 4, "cl07_100_09": 4, "cl07_100_10": 4, "cl08_020_01": 4, "cl08_020_02": 4, "cl08_020_03": 4, "cl08_020_04": 4, "cl08_020_05": 4, "cl08_020_06": 4, "cl08_020_07": 4, "cl08_020_08": 4, "cl08_020_09": 4, "cl08_020_10": 4, "cl08_040_01": 4, "cl08_040_02": 4, "cl08_040_03": 4, "cl08_040_04": 4, "cl08_040_05": 4, "cl08_040_06": 4, "cl08_040_07": 4, "cl08_040_08": 4, "cl08_040_09": 4, "cl08_040_10": 4, "cl08_060_01": 4, "cl08_060_02": 4, "cl08_060_03": 4, "cl08_060_04": 4, "cl08_060_05": 4, "cl08_060_06": 4, "cl08_060_07": 4, "cl08_060_08": 4, "cl08_060_09": 4, "cl08_060_10": 4, "cl08_080_01": 4, "cl08_080_02": 4, "cl08_080_03": 4, "cl08_080_04": 4, "cl08_080_05": 4, "cl08_080_06": 4, "cl08_080_07": 4, "cl08_080_08": 4, "cl08_080_09": 4, "cl08_080_10": 4, "cl08_100_01": 4, "cl08_100_02": 4, "cl08_100_03": 4, "cl08_100_04": 4, "cl08_100_05": 4, "cl08_100_06": 4, "cl08_100_07": 4, "cl08_100_08": 4, "cl08_100_09": 4, "cl08_100_10": 4, "cl09_020_01": 4, "cl09_020_02": 4, "cl09_020_03": 4, "cl09_020_04": 4, "cl09_020_05": 4, "cl09_020_06": 4, "cl09_020_07": 4, "cl09_020_08": 4, "cl09_020_09": 4, "cl09_020_10": 4, "cl09_040_01": 4, "cl09_040_02": 4, "cl09_040_03": 4, "cl09_040_04": 4, "cl09_040_05": 4, "cl09_040_06": 4, "cl09_040_07": 4, "cl09_040_08": 4, "cl09_040_09": 4, "cl09_040_10": 4, "cl09_060_01": 4, "cl09_060_02": 4, "cl09_060_03": 4, "cl09_060_04": 4, "cl09_060_05": 4, "cl09_060_06": 4, "cl09_060_07": 4, "cl09_060_08": 4, "cl09_060_09": 4, "cl09_060_10": 4, "cl09_080_01": 4, "cl09_080_02": 4, "cl09_080_03": 4, "cl09_080_04": 4, "cl09_080_05": 4, "cl09_080_06": 4, "cl09_080_07": 4, "cl09_080_08": 4, "cl09_080_09": 4, "cl09_080_10": 4, "cl09_100_01": 4, "cl09_100_02": 4, "cl09_100_03": 4, "cl09_100_04": 4, "cl09_100_05": 4, "cl09_100_06": 4, "cl09_100_07": 4, "cl09_100_08": 4, "cl09_100_09": 4, "cl09_100_10": 4, "cl10_020_01": 4, "cl10_020_02": 4, "cl10_020_03": 4, "cl10_020_04": 4, "cl10_020_05": 4, "cl10_020_06": 4, "cl10_020_07": 4, "cl10_020_08": 4, "cl10_020_09": 4, "cl10_020_10": 4, "cl10_040_01": 4, "cl10_040_02": 4, "cl10_040_03": 4, "cl10_040_04": 4, "cl10_040_05": 4, "cl10_040_06": 4, "cl10_040_07": 4, "cl10_040_08": 4, "cl10_040_09": 4, "cl10_040_10": 4, "cl10_060_01": 4, "cl10_060_02": 4, "cl10_060_03": 4, "cl10_060_04": 4, "cl10_060_05": 4, "cl10_060_06": 4, "cl10_060_07": 4, "cl10_060_08": 4, "cl10_060_09": 4, "cl10_060_10": 4, "cl10_080_01": 4, "cl10_080_02": 4, "cl10_080_03": 4, "cl10_080_04": 4, "cl10_080_05": 4, "cl10_080_06": 4, "cl10_080_07": 4, "cl10_080_08": 4, "cl10_080_09": 4, "cl10_080_10": 4, "cl10_100_01": 4, "cl10_100_02": 4, "cl10_100_03": 4, "cl10_100_04": 4, "cl10_100_05": 4, "cl10_100_06": 4, "cl10_100_07": 4, "cl10_100_08": 4, "cl10_100_09": 4, "cl10_100_10": 4, "class": [0, 1, 4, 5, 6, 7, 8, 9, 11, 12, 15, 16, 17, 18], "class_10_100": [3, 4], "class_10_20": [3, 4], "class_10_40": [3, 4], "class_10_60": [3, 4], "class_10_80": [3, 4], "class_1_100": [3, 4], "class_1_20": [3, 4], "class_1_40": [3, 4], "class_1_60": [3, 4], "class_1_80": [3, 4], "class_2_100": [3, 4], "class_2_20": [3, 4], "class_2_40": [3, 4], "class_2_60": [3, 4], "class_2_80": [3, 4], "class_3_100": [3, 4], "class_3_20": [3, 4], "class_3_40": [3, 4], "class_3_60": [3, 4], "class_3_80": [3, 4], "class_4_100": [3, 4], "class_4_20": [3, 4], "class_4_40": [3, 4], "class_4_60": [3, 4], "class_4_80": [3, 4], "class_5_100": [3, 4], "class_5_20": [3, 4], "class_5_40": [3, 4], "class_5_60": [3, 4], "class_5_80": [3, 4], "class_6_100": [3, 4], "class_6_20": [3, 4], "class_6_40": [3, 4], "class_6_60": [3, 4], "class_6_80": [3, 4], "class_7_100": [3, 4], "class_7_20": [3, 4], "class_7_40": [3, 4], "class_7_60": [3, 4], "class_7_80": [3, 4], "class_8_100": [3, 4], "class_8_20": [3, 4], "class_8_40": [3, 4], "class_8_60": [3, 4], "class_8_80": [3, 4], "class_9_100": [3, 4], "class_9_20": [3, 4], "class_9_40": [3, 4], "class_9_60": [3, 4], "class_9_80": [3, 4], "class_and_beng": [3, 4], "classic": [0, 1], "classif": [17, 18], "clear": 11, "clone": [0, 1], "close": [0, 1, 4, 5, 8, 11], "closer": [0, 1, 6, 7, 11], "closest": [0, 1, 9, 11], "cl\u00e1udio": 4, "cma": [6, 8], "cmae": [3, 4, 6, 8], "cmaes_raw": [3, 8], "cmaes_surrog": [3, 8], "cn": [0, 1, 4, 5, 6, 7, 12, 15, 17], "cn11": 15, "co": 8, "coars": 7, "code": [0, 4, 5, 6, 7, 9, 12, 15, 17], "codegen": [3, 8], "codegener": [8, 9], "codeql": [0, 1], "coeffici": 9, "col": 8, "collect": [0, 1, 8], "collector": [3, 4, 8, 15], "colon": 4, "color": [4, 8], "column": [4, 17], "com": [0, 1, 4, 5, 6, 7], "combin": [0, 1, 6, 7, 8, 9, 12], "combinatori": [4, 12, 15], "come": [0, 1, 11, 15], "comjnl": 4, "comment": [0, 1, 3, 4, 15], "common": [0, 1], "comopt": [15, 16], "compact": [4, 17], "compar": [4, 6, 8], "comparison": [4, 12], "compet": 17, "compil": 9, "complet": [0, 1, 8], "complex": [5, 9], "compli": [0, 1], "complianc": [0, 1], "compliant": 4, "complic": 8, "compon": [0, 1, 3, 4, 5, 6, 8, 11, 12, 15, 17], "compos": [8, 9], "comprehens": 4, "compris": [4, 8], "comput": [0, 1, 4, 6, 7, 8, 9, 11, 12, 15, 16, 17], "con20": 17, "concaten": 8, "conceiv": 8, "concept": 8, "concern": [0, 1], "condit": 4, "confer": 4, "configur": 8, "conjunct": 17, "connect": [0, 1, 8, 15], "consid": [4, 5, 8, 11, 17], "consist": [0, 1], "constant": [3, 4, 8], "constraint": [0, 1, 4, 17], "construct": [0, 1, 8], "constructor": [4, 8], "consum": [4, 8, 9], "consumpt": 15, "contain": [0, 1, 4, 8, 11, 14, 15, 17], "content": [4, 6, 8, 17], "continu": [0, 1, 5, 6], "contradict": 8, "contribut": [0, 1, 4, 5, 6, 7, 15], "contrl": 8, "contrl2": 8, "contrl3": 8, "control": [2, 3, 10, 15], "control_dim": [3, 8, 9], "controller_dim": 8, "controller_spac": 8, "controller_training_algorithm": 8, "convent": [0, 1], "converg": 10, "convers": [8, 10], "convert": [4, 6, 7, 11, 15, 17], "cook": 15, "cool": 11, "coordin": [0, 1, 4, 7, 8, 9, 10, 11, 15], "copi": [0, 1, 3, 4, 6, 15, 17], "copyright": [0, 1, 3], "cor": 4, "coral": [0, 1, 12, 13], "cordier": 10, "cornejo": [8, 9, 10], "corner": 5, "correct": [0, 1, 4, 8], "correctli": [0, 1, 11], "correspond": [4, 6, 7, 8, 12, 15, 17], "cosmet": 8, "cost": [8, 9], "costli": [8, 9, 17], "could": [0, 1, 4, 5, 6, 7, 8, 9, 11, 15, 17], "count": [4, 6, 7, 17], "count_best_ov": 4, "count_error": [3, 17], "counter": [5, 7], "coupl": 10, "cours": [0, 1, 6, 8, 9, 11, 15, 17], "cover": [6, 7], "coverag": [0, 1], "cp": 17, "cpaior": 4, "creat": [0, 1, 3, 4, 6, 8, 9, 10, 14, 15, 17], "criterion": 15, "crockett": 15, "cross": 8, "crosstalk": 10, "crude": [8, 15], "csv": [3, 4], "csvreader": [3, 4], "csvwriter": [3, 4], "ctrl": [0, 1, 8], "cubic": [3, 8], "cumbersom": 8, "current": [0, 1, 3, 5, 6, 7, 8, 9, 15, 17], "cut": [4, 6, 17], "cutter": 6, "cypru": 17, "d": [0, 1, 8, 9, 12, 17], "daan": [4, 15], "dai": [0, 1, 17], "dam": 17, "damv": 4, "darmstadt": 12, "data": [0, 1, 3, 4, 5, 6, 7, 8, 11, 12, 13, 15, 16, 17, 18], "data_analytics2016b": 4, "dataset": [0, 1, 4], "david": [15, 17, 18], "dbb": [8, 9, 10], "dd": 17, "de": [4, 12, 15, 16], "deactiv": 8, "dead": [0, 1], "deal": [4, 11], "decemb": [15, 17], "decid": [6, 8, 15], "decod": [4, 5, 6, 17], "decreas": [0, 1, 4, 11], "deep": [8, 9, 10], "def": [8, 11], "default": [3, 4, 6, 8, 9, 11, 12, 14, 15, 17], "default_executor": [4, 6], "default_height_per_bin": 4, "default_object": [3, 4], "default_packing_item_str": [3, 4], "default_width_per_bin": 4, "defin": [0, 1, 4, 5, 6, 8, 9, 11, 15, 17], "definit": [6, 7, 15], "deg2rad": 8, "degre": [0, 1, 7], "den": [4, 15], "denot": [0, 1, 6, 17], "depart": 12, "depend": [0, 1, 8, 9, 15, 17], "dependabot": [0, 1], "dequan": [4, 5], "der": [8, 9, 10], "deriv": [0, 1, 11], "describ": [0, 1, 4, 6, 8, 9, 11, 17], "describe_parameter": [3, 8], "describe_system": [3, 8], "describe_system_without_control": [3, 8], "descript": [3, 6, 17], "design": 17, "dest": [4, 6, 8, 17], "dest_dir": [4, 8], "dest_fil": [4, 8], "destin": [4, 6, 8, 17], "destroi": 8, "detail": [0, 1, 8], "detect": [0, 1], "determin": [0, 1, 15], "determinist": [6, 17], "develop": [0, 1, 5, 6, 8], "deviat": 6, "diagon": 11, "diamet": 10, "dict": 15, "dictat": [0, 1], "did": 5, "diff_from_od": [3, 8], "differ": [0, 1, 3, 4, 5, 6, 7, 8, 9, 11, 15, 17], "differenti": [0, 1, 3, 8, 9, 10], "dim": 8, "dimac": 12, "dimens": [0, 1, 6, 8, 9, 11, 17], "dimension": [3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 17], "ding": 12, "dip": 6, "dipti": 15, "direct": [6, 15], "directli": [0, 1, 4, 8, 9, 11, 15, 16, 17], "directori": [4, 6, 8], "dirnam": [15, 17], "disappear": 7, "discov": 8, "discret": [15, 17], "discuss": 6, "disjoint": 15, "disregard": 8, "dist": [11, 15], "distanc": [0, 1, 2, 3, 8, 12, 15, 17], "distant": 11, "distinguish": [7, 8, 17], "distribut": [0, 1, 6, 7, 17], "diverg": 8, "divers": 8, "divid": [0, 1, 6, 8, 11, 17], "dizdarev": 15, "dm": 11, "do": [0, 1, 4, 5, 6, 7, 8, 9, 11, 15, 16, 17], "do_log_h": [3, 15], "docstr": [0, 1], "document": [0, 1, 15, 16, 17, 18], "dodgi": [0, 1], "doe": [0, 1, 4, 5, 7, 8, 9, 11, 15, 16, 17, 18], "doi": [4, 5, 8, 9, 10, 12, 13, 15, 16, 17], "domain": [0, 1], "domin": 8, "done": [7, 8, 11], "doubl": [0, 1, 10, 17], "down": [0, 1, 5], "download": [4, 17], "download_2dpacklib_inst": [3, 4], "downward": 5, "dpi": 4, "dr": [0, 1, 4, 5, 6, 7, 8, 10, 12, 15, 17], "drawn": 4, "drive": [6, 7, 8, 15], "driven": [8, 17, 18], "drop": 9, "dst": 12, "dt": [0, 1, 8, 9], "dtype": [4, 11, 12, 15, 17], "du": 12, "due": [0, 1, 6, 15], "dummi": 15, "duplic": 17, "dure": [0, 1, 8, 11], "duw": 7, "dx": [0, 1], "dy": [0, 1], "dyn": 10, "dynam": [3, 8, 9, 10], "dynamic_control": [1, 2, 3], "e": [0, 1, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15, 17], "ea": 15, "ea1p1_revn": [2, 3], "each": [0, 1, 4, 5, 6, 8, 9, 11, 12, 15, 17], "ealgfi": [3, 4], "ealgfiea": 4, "ealgfilgfi": 4, "earliest": [0, 1, 17], "eas": [0, 1], "easi": [6, 8], "easier": [4, 5, 8, 15], "easili": [0, 1, 8, 9, 11, 15], "easton": 17, "econom": [17, 18], "ed": 12, "edg": [0, 1, 15], "edit": [8, 9, 10, 15], "editor": 15, "edu": [0, 1, 4, 5, 6, 7, 12, 13, 15, 17], "educ": [0, 1], "effect": 15, "effici": [0, 1, 9, 15], "effort": 8, "eil101": 15, "eil51": 15, "eil76": 15, "either": [0, 1, 8, 11, 15, 17], "ejor": [12, 17], "element": [3, 4, 5, 6, 7, 11, 12, 17], "eleven": 15, "elian": 12, "els": [7, 8, 11, 17], "elsewher": 11, "email": [0, 1], "emiel": 4, "emploi": 8, "empti": [4, 5, 7, 17], "emptiest": 7, "empty": 7, "emul": 6, "en": 4, "encapsul": 12, "encod": [0, 1, 3, 4, 6, 7, 9, 14, 17], "encompass": 17, "encount": 5, "end": [0, 1, 3, 4, 5, 6, 8, 9, 11, 17], "end0": 8, "end1": 8, "end_color": [3, 8], "end_i": [4, 15, 17], "end_mark": [3, 8], "end_result": [3, 4], "end_statist": [3, 4], "endlin": [8, 9], "endresult": 4, "endstatist": 4, "energi": [8, 9], "enforc": [0, 1], "enhanc": [8, 9, 10], "enough": [0, 1, 4], "ensur": [4, 6, 17], "enter": 9, "entir": 8, "entri": 17, "enumer": 15, "environ": [0, 1], "epilog": 3, "epilogu": 3, "equal": [0, 1, 5, 6, 7, 8, 11, 15, 17], "equat": [0, 1, 3, 8, 9, 10, 15], "erad": [0, 1], "eranda": 12, "erik": 4, "error": [0, 1, 2, 3, 4, 8], "errors_and_hard": [3, 4], "errorsandhard": [4, 6], "escap": 8, "especi": [0, 1], "essenti": 8, "estim": [7, 8, 15], "etc": 6, "euclidean": 15, "eugen": 15, "eurika": 10, "european": [4, 5, 12, 17], "evalu": [3, 4, 6, 7, 8, 9, 12, 15, 17], "evaluationdatael": 4, "even": [0, 1, 4, 8, 11, 17], "eventu": [6, 7], "everi": [0, 1, 5, 6, 8, 15, 17], "everyth": 8, "everytim": 8, "evolut": 8, "exact": [4, 6, 17], "exactli": [0, 1, 4, 5, 6, 8, 11, 15, 17], "exampl": [0, 1, 4, 6, 8, 9, 10, 11, 15, 16, 17, 18], "exce": [4, 9], "except": [7, 16, 18], "exchang": [0, 1], "exclus": 17, "execut": [4, 6, 8], "executor": [4, 6], "exhaust": [8, 15], "exist": [0, 1, 4, 6, 8, 11], "exp": [8, 9], "expect": 8, "expend": [0, 1], "expens": 8, "experi": [0, 1, 2, 3, 8, 9, 14, 15], "experiment": [0, 1], "experiment_raw": [2, 3], "experiment_surrog": [2, 3], "explain": 17, "explod": 8, "explor": [0, 1], "expm1": 8, "express": [0, 1], "expung": 7, "extend": [4, 5, 8], "extens": [0, 1, 11, 17], "extern": 15, "extract": [4, 8, 11], "extrem": 8, "f": [0, 1, 6, 8, 11, 12], "fabien": 10, "facil": [0, 1, 11, 12], "factor": [5, 7], "factori": [0, 1, 4, 14], "faculti": [17, 18], "fail": [0, 1, 8], "fair": [0, 1, 17], "fall": [0, 1], "fals": [4, 6, 8, 15, 17], "fanci": 8, "fancy_log": [3, 8], "faq": [15, 16], "far": [0, 1, 5, 6, 8, 11, 17], "farther": [0, 1, 8, 11], "farthest": [15, 17], "fashion": [8, 17], "fast": 8, "faster": [0, 1, 5, 8], "fastmath": 9, "fe": [4, 6, 8, 14], "fea": [3, 4, 15], "fea1p1_revn": [2, 3], "feasibl": [0, 1, 4, 15, 17], "featur": 6, "ferland": 12, "fes_for_train": [3, 8], "fes_for_warmup": [3, 8], "fes_per_model_run": [3, 8], "few": [0, 1], "fewer": [7, 8], "fewest": 7, "ff1993ghftqap": 12, "ffa": [0, 1], "fifth": 4, "figur": [4, 8], "figureofmerit": [3, 8], "figureofmeritl": [3, 8], "file": [0, 1, 3, 4, 5, 8, 13, 15, 16, 17, 18], "file_nam": [13, 16, 18], "fill": [4, 7, 8, 17], "final": [3, 4, 6, 7, 8, 9, 10, 11, 12, 15, 17], "find": [0, 1, 3, 6, 8, 9, 11, 15, 16, 17, 18], "finish": 8, "finit": [0, 1, 4], "first": [0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 15, 17], "fit": [0, 1, 4, 5, 6, 9, 15], "five": [5, 6], "fix": [0, 1, 6, 11], "flake8": [0, 1], "flatten": [4, 8, 15], "fleurent": 12, "flight": 8, "float": [0, 1, 4, 6, 8, 9, 11, 12, 15], "florian": 4, "flow": [0, 1, 3, 4, 11, 12], "flow_pow": [3, 11], "flu": 10, "fluid": [8, 9, 10], "fluidic": [8, 9, 10], "flw": 12, "fly": 9, "flyer": [0, 1], "foci": 15, "focus": 7, "focuss": 7, "folder": 4, "follow": [0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 12, 15, 17], "font": 4, "footer": [3, 4], "forc": [0, 1, 8, 17], "form": [4, 8], "format": [0, 1, 4, 12, 15, 17], "format_best": 4, "formatt": 4, "former": [0, 1, 8], "forward": 7, "found": [12, 15, 16, 18], "foundat": [0, 1, 15], "four": [0, 1, 4, 6, 11], "fraction": 11, "frame": 8, "framework": 15, "franz": [12, 13], "fran\u00e7oi": [8, 9, 10], "free": [0, 1, 7, 8, 17], "frequenc": [10, 15], "frequent": [0, 1], "fri26": 15, "from": [0, 1, 4, 5, 6, 7, 8, 9, 11, 12, 15, 17], "from_2dpacklib": [3, 4], "from_compact_str": [3, 4, 6], "from_csv": [3, 4], "from_fil": [3, 15, 17], "from_log": [3, 4], "from_packing_and_end_result": [3, 4], "from_packing_result": [3, 4], "from_qaplib_stream": [3, 12], "from_resourc": [3, 4, 6, 12, 15, 17], "from_sequence_and_dist": [3, 11], "from_single_log": [3, 4], "from_str": [3, 4, 6, 11, 17], "fstring": [0, 1], "ft53": 15, "ft70": 15, "ftv170": 15, "ftv33": 15, "ftv35": 15, "ftv38": 15, "ftv44": 15, "ftv47": 15, "ftv55": 15, "fulfil": 8, "full": [0, 1, 5, 15], "fulli": [0, 1, 5, 15], "func": 8, "function": [0, 1, 4, 6, 7, 8, 9, 11, 12, 14, 15, 17], "further": [4, 6], "f\u00fcr": [15, 16], "g": [0, 1, 3, 6, 8, 9, 11, 17], "gal4": 17, "gambardella": 12, "game": [0, 1, 17], "game_encod": [2, 3], "game_plan": [2, 3], "game_plan_dtyp": [3, 17], "game_plan_length": [3, 17], "game_plan_spac": [2, 3], "gameencod": [3, 17], "gameplan": [3, 17], "gameplandtyp": 17, "gameplanlength": [3, 17], "gameplanspac": [3, 17], "gamma": [3, 8], "gantt": 5, "gather": [4, 8], "gener": [0, 1, 3, 6, 8, 9, 14, 17], "genet": [4, 5, 10, 12, 15], "geo": 15, "geograph": 15, "geometr": 4, "georg": [15, 17], "gerhard": [0, 1, 15, 16], "germani": [4, 12, 15, 16, 17], "get": [0, 1, 4, 6, 7, 8, 9, 11, 12, 14, 15, 17], "get_bibtex": [3, 4], "get_column_titl": [3, 4], "get_differenti": [3, 8], "get_dist": 11, "get_footer_bottom_com": [3, 4], "get_footer_com": [3, 4], "get_header_com": [3, 4], "get_log": [4, 15, 17], "get_optimal_plan_length_bound": [3, 17], "get_related_work": [3, 4], "get_row": [3, 4], "get_standard_item_sequ": [3, 4], "get_tag": 11, "get_x_dim": [4, 6], "getvalu": 8, "ghent": [17, 18], "git": [0, 1], "github": [0, 1], "give": [0, 1, 6, 8, 11, 17], "given": [0, 1, 4, 5, 6, 7, 8, 9, 11, 15, 17], "global": [12, 13, 15], "gnu": [0, 1], "go": [0, 1, 6, 8, 11, 17], "goal": [0, 1, 3, 6, 8, 9, 10, 11, 12, 15], "golden": 9, "good": [0, 1, 6, 8, 11, 12, 15], "goossen": [0, 1], "got": [0, 1], "govern": [8, 9, 10], "gr120": 15, "gr2013abrkgaf2a3bpp": 4, "gr202": 15, "gr24": 15, "gr48": 15, "gr666": 15, "gr96": 15, "graph": [0, 1, 15], "grasp": 4, "grasp_vnd": [3, 4], "graspvndgrasp": 4, "graspvndvnd": 4, "gregori": 15, "group": [4, 6, 17, 18], "groups_to_inst": [3, 4], "grow": 8, "guajardo": [0, 1], "gui": [8, 9, 10], "guid": 15, "guidelin": [0, 1], "guillotin": 4, "gutin": 15, "h": [4, 15, 17], "ha": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 17], "hack": [0, 1, 8], "had": [8, 11], "hahn": [0, 1, 12], "handbook": 12, "handl": [0, 1, 4, 8, 15], "happen": [0, 1], "harambat": 10, "harbin": [8, 9, 10, 15], "hard": [0, 1, 3, 4], "have": [0, 1, 4, 5, 6, 7, 8, 9, 11, 15, 17], "hdl": 4, "header": 4, "heap": 8, "hefei": [0, 1, 4, 5, 6, 7, 12, 15, 17], "heidelberg": [15, 16, 17], "heigh": 4, "height": [0, 1, 4, 5, 6, 7], "helicopt": 9, "help": [0, 1], "henc": [4, 11], "hendrik": 15, "henri": 12, "here": [0, 1, 4, 5, 6, 8, 9, 10, 11, 15, 17, 18], "heurist": [0, 1, 4, 5, 17], "hfuu": [0, 1, 4, 5, 6, 7, 12, 15, 17], "hhano": 4, "hhano_r": [3, 4], "hhano_sr": [3, 4], "hhanor": 4, "hhanosr": 4, "hidden": 9, "hierarchi": 4, "high": [0, 1, 11, 15], "higher": [6, 11], "highest": [7, 17], "him": [0, 1], "hint": [0, 1], "hisao": 15, "hold": [0, 1, 4, 7, 8, 11, 15, 16, 17, 18], "hole": 7, "home": [0, 1, 17], "home_idx": 17, "home_streak_max": [3, 17], "home_streak_min": [3, 17], "homestreakmax": 17, "homestreakmin": 17, "hometown": 17, "hong": 15, "hongfei": [4, 5], "hope": [0, 1, 17], "hopefulli": 8, "horizon": [3, 11], "horizont": [0, 1, 6, 7, 11], "how": [4, 6, 8, 11, 17], "howev": [0, 1, 8, 9, 11, 17], "html": [15, 16], "http": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 16, 17, 18], "hundr": [0, 1], "hurdl": [0, 1], "hurrai": 6, "hurt": [0, 1], "hwee": 15, "hybrid": 12, "i": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 17], "iao": [0, 1, 4, 5, 6, 7, 12, 15, 17], "iaria": 4, "ibl_encoding_1": [3, 4], "ibl_encoding_2": [3, 4], "id": [4, 5, 17], "idea": [4, 6, 7, 8, 9, 11, 17], "ideal": 8, "identifi": [0, 1, 4, 5, 12, 15], "idsia": 12, "idx": 8, "idx_bin": [3, 4], "idx_bottom_i": [3, 4], "idx_height": [3, 4], "idx_id": [3, 4], "idx_left_x": [3, 4], "idx_repetit": [3, 4], "idx_right_x": [3, 4], "idx_top_i": [3, 4], "idx_width": [3, 4], "ieee": 15, "ifi": [15, 16], "ignor": [0, 1, 3, 7, 8, 9, 11, 17], "ijoc": [15, 16], "ill": [8, 15], "illustr": 8, "imagin": [0, 1, 8, 9, 11, 17], "immedi": [4, 5], "immut": 4, "impact": [7, 8, 11], "implement": [0, 1, 3, 4, 5, 7, 8, 12, 15, 17], "impli": [0, 1], "import": [0, 1, 4, 5, 6, 7, 8, 11, 15, 17], "importance_to_font_s": 4, "importance_to_font_size_func": 4, "impos": 17, "improv": [0, 1, 4, 5, 8, 12], "improvedbottomleftencoding1": [4, 5], "improvedbottomleftencoding2": [4, 5], "impuls": 8, "inappropri": 8, "includ": [0, 1, 4, 5, 6, 9, 12, 13, 14, 15, 17, 18], "incomprehensibli": 4, "incorpor": [6, 8], "increas": [0, 1, 5, 6, 7, 8, 9], "increment": [4, 11], "incur": 17, "inde": [0, 1, 4, 6, 17], "indent": [8, 9], "index": [0, 1, 4, 5, 6, 8, 11, 15, 17], "indexzerobas": 11, "indic": [0, 1, 5, 6, 11, 17], "indirectli": 7, "industri": [0, 1], "infeas": 17, "infin": 8, "infinit": 8, "influenc": [0, 1, 8], "inform": [0, 1, 4, 8, 12, 17], "informatica": 12, "infrastructur": [0, 1], "inherit": 17, "initi": [0, 1, 3, 4, 5, 6, 7, 8, 10, 15], "inmemorylogg": [4, 15, 17], "inner_max_f": [4, 6], "inner_run": [4, 6], "input": [0, 1, 5, 6, 8, 9, 10, 14, 17], "ins": [4, 7, 12], "insert": 17, "insid": [4, 5, 6], "inspir": 6, "inst": [3, 4, 12, 14, 15, 17], "inst_decod": [3, 4], "inst_group_select": 4, "inst_group_sort_kei": [3, 4], "inst_nam": [4, 6], "insta": [12, 17], "instanc": [0, 1, 2, 3, 5, 6, 7, 9, 13, 14, 18], "instance_spac": [3, 4], "instancedecod": [4, 6], "instances_resourc": [3, 4], "instances_to_group": [3, 4], "instancespac": [4, 6], "instanti": [4, 17], "instead": [0, 1, 6, 7, 8, 9, 11, 15, 17], "instgen": [3, 4], "institut": [0, 1, 4, 5, 6, 7, 8, 9, 10, 12, 15, 16, 17], "insuffici": 4, "int": [4, 6, 7, 8, 9, 10, 11, 12, 14, 15, 17], "int16": [4, 12], "int8": [4, 7], "int_range_to_dtyp": 17, "integ": [0, 1, 6, 7, 11, 15, 17], "integr": [4, 8], "intellig": [0, 1, 4, 5, 6, 7, 12, 15, 17], "intens": 8, "intent": 7, "interest": [0, 1, 6, 8, 15], "interesting_point_object": [3, 8], "interesting_point_transform": [3, 8], "interestingli": 15, "interfac": 4, "intern": [3, 4, 6, 8], "internal_sep": [3, 4], "interpol": 8, "intersci": 15, "intersect": [7, 15], "interv": [6, 8], "invalid": [6, 14], "invers": 17, "invert": 8, "invok": [0, 1, 8], "involv": [8, 17], "inza": 15, "io": 8, "iori": 4, "is_always_integ": [3, 4, 6, 7, 17], "is_equ": [3, 4, 6, 17], "is_symmetr": [3, 15], "isbn": [4, 15, 17], "ise": [0, 1, 12, 13], "ishibuchi": 15, "isinst": 3, "issu": [0, 1], "itali": 4, "item": [4, 5, 6, 7, 17], "item_height_max": [4, 6], "item_height_min": [4, 6], "item_id": 4, "item_in_bin_index": 4, "item_index": 4, "item_width_max": [4, 6], "item_width_min": [4, 6], "iter": [3, 4, 6, 8, 9, 11, 12, 14, 15, 17], "its": [0, 1, 4, 5, 6, 8, 9, 10, 11, 15, 17], "itself": [5, 7, 17], "i\u00f1aki": 15, "j": [0, 1, 4, 8, 11, 12, 15, 17], "j_from_od": [3, 8], "jacqu": [10, 12], "jan": 15, "januari": [4, 5, 8, 9, 10, 12], "jet": [8, 9, 10], "jiayang": 12, "john": 15, "join": [4, 8, 15, 17], "join_instances_to_compact": [3, 4], "jor": 4, "jos\u00e9": 4, "journal": [4, 5, 12, 13, 15, 16, 17], "juli": 12, "jump": 17, "june": [0, 1, 4, 17], "just": [0, 1, 6, 7, 8, 11, 15, 17], "j\u00f6rg": 15, "k": [0, 1, 15, 17], "kaiser": 10, "kan": 15, "karel": 15, "karisch": [0, 1, 12, 13], "kauna": 12, "ke": 15, "keelei": 15, "keep": 10, "kei": [0, 1, 4, 5, 8, 11, 12], "kelli": 17, "keong": 15, "key_bin_height": [3, 4], "key_bin_width": [3, 4], "key_n_different_item": [3, 4], "key_n_item": [3, 4], "key_valu": [4, 15, 17], "keyvaluelogsect": [4, 5, 6, 8, 11, 12, 15, 17], "kick": 8, "kind": 17, "kluwer": 15, "km": 15, "know": [0, 1, 7, 11, 17], "known": [0, 1, 3, 4, 12, 15], "known_optima": [2, 3], "kong": 15, "kroa100": 15, "kroc100": 15, "krod100": 15, "kroe100": 15, "kuijper": 15, "kunm": 15, "kv": [4, 15, 17], "kwoh": 15, "l": [4, 15, 17], "l2008agaftdbpp": 4, "label": 4, "laid": 4, "lambda": [4, 8, 11, 15], "landau": [0, 1, 8, 10], "lane": 15, "larg": [4, 6, 8, 11, 15], "larger": [5, 6, 8, 11, 15], "largest": [7, 12], "larra\u00f1aga": 15, "last": [0, 1, 4, 5, 7, 8, 15, 17], "later": [0, 1, 6], "latest": [0, 1], "latex": 4, "latitud": 15, "latter": [0, 1, 4], "laurent": 10, "law": [0, 1], "lawler": 15, "layer": [8, 9], "layout": [0, 1], "lb": [6, 7, 12], "lead": [0, 1, 4, 6, 17], "leagu": 17, "learn": [8, 9, 10], "least": [0, 1, 6, 7, 11, 17], "lee": 15, "left": [0, 1, 4, 5, 7, 11], "leg": 17, "lehigh": [0, 1, 12, 13], "leighton": 15, "len": [4, 8, 11, 12, 15, 17], "length": [0, 1, 4, 6, 8, 11, 12, 15, 17], "lenstra": 15, "leonida": 12, "less": [0, 1, 7, 9, 15, 17], "let": [6, 7, 8, 9, 11, 17], "letter": 4, "level": 4, "lgfi": 4, "li": [0, 1, 8, 9, 10], "liang": [0, 1, 15], "liangti": [0, 1, 15], "lib": [12, 15], "librari": [0, 1, 4, 12, 13, 15, 16], "like": [0, 1, 6, 8, 9, 11], "lima": 4, "limit": [0, 1, 5, 8, 17], "lin105": 15, "line": [0, 1, 4, 5, 8, 9], "linear": [3, 8, 10], "link": 17, "linter": [0, 1], "linux": [0, 1], "list": [0, 1, 4, 6, 9, 15], "list_resourc": [3, 4, 12, 15, 17], "list_resource_tour": [3, 15], "list_resources_group": [3, 4], "literatur": 4, "lithuania": 12, "littl": [0, 1, 5, 8, 11], "liu": [4, 5], "load": [0, 1, 4, 12, 15, 17], "local": [0, 1, 6, 15], "locat": [0, 1, 4, 7, 8, 10, 11, 12, 17], "log": [4, 5, 6, 8, 11, 12, 15, 17], "log_fil": 8, "log_parameters_to": [3, 4, 5, 6, 8, 11, 12, 15, 17], "logger": [4, 5, 6, 8, 11, 12, 15, 17], "logic": [0, 1, 17], "logist": 17, "logp1": 8, "loiola": 12, "long": [8, 11, 17], "longer": [0, 1, 5, 15, 17], "longest": 17, "longitud": 15, "look": [0, 1, 5, 8], "loop": [6, 8, 15], "lorenz": [3, 8], "lorenz_111": [8, 10], "lorenz_4": [8, 10], "lot": [0, 1, 8], "loti": 4, "low": 11, "lower": [6, 7, 8, 11, 12, 15, 17], "lower_bound": [3, 4, 6, 7, 8, 12, 15, 17], "lower_bound_bin": [3, 4, 6, 7], "lower_bound_gett": [15, 17], "lower_bounds_bin_count": [3, 4], "lowerbound": 4, "lowest": [7, 17], "luca": 12, "lucki": 7, "lugano": 12, "lusseyran": [8, 9, 10], "l\u00e4ssig": 15, "m": [0, 1, 4, 8, 9, 12, 15], "m2003amsaaftqap": 12, "m2005atsaftqap": 12, "m2008aiotitsaftqap": 12, "maceda": [8, 9, 10], "macedo": 4, "machin": [0, 1, 8, 9, 10], "magazin": 15, "mai": [0, 1, 4, 5, 6, 8, 11, 12, 15, 17], "maia": 12, "main": 11, "mainli": [0, 1, 11, 15], "maintain": [0, 1, 17, 18], "make": [0, 1, 4, 6, 7, 8, 11, 14, 15, 16, 17], "make_2dpacklib_resourc": [3, 4], "make_3_couple_oscil": [8, 10], "make_ann": [8, 9], "make_comparison_t": [3, 4], "make_comparison_table_data": [3, 4], "make_inst": [2, 3, 8], "make_interesting_starting_point": [3, 8], "make_lorenz": [8, 10], "make_packing_invalid": [3, 14], "make_packing_valid": [3, 14], "make_stuart_landau": [8, 10], "make_tour_invalid": [3, 14], "make_tour_valid": [3, 14], "man": 9, "mani": [4, 7, 8, 11, 15, 17], "manner": [0, 1], "mannner": 17, "manuel": 4, "map": [4, 5, 6, 11, 15, 17], "map_gam": [3, 17], "mappingproxi": 4, "march": 4, "maria": 12, "mark": [4, 8], "marker": 8, "martello": 4, "master": [0, 1, 4, 5, 6, 7, 12, 15, 17], "match": [0, 1, 4], "materi": [0, 1], "math": [4, 6], "mathemat": [8, 9, 12, 15, 17], "mathematik": [15, 16], "matric": [8, 15, 17], "matrix": [0, 1, 4, 8, 11, 12, 15, 17], "matter": [0, 1], "mavdc2010afmfttdgcsp": 4, "max": [7, 12, 17], "max_bins_per_row": 4, "max_f": [3, 4, 6, 8, 14], "max_height": 4, "max_radiu": 8, "max_row": 4, "max_tim": 8, "max_width": 4, "maxim": [7, 8, 11, 17], "maximum": [4, 6, 8, 11, 14, 17], "mayb": [0, 1, 8, 11, 15], "mci": 15, "me": [0, 1], "mean": [0, 1, 4, 5, 6, 7, 8, 11, 15, 17], "measur": [0, 1, 11], "mechan": [7, 8, 9, 10], "med": 4, "median": 4, "memori": [8, 9, 12, 15], "merchant": [0, 1], "merg": [6, 8], "merit": 8, "metaheurist": [0, 3], "method": [0, 1, 3, 4, 5, 8, 11, 12, 15, 17], "metric": 11, "mgi": [0, 1, 12, 13], "miao": 15, "michael": 17, "michalewicz": 15, "michel": 4, "might": [0, 1, 5], "miguel": [0, 1], "millisecond": 8, "min": [7, 12], "min_ann": [3, 8], "min_area": 7, "min_bin": [4, 6], "min_item": 7, "mineiro": 17, "minim": [0, 1, 4, 7, 8, 9, 11, 12, 17], "minimum": [4, 6, 7, 11, 17], "minu": [7, 17], "mirror": 17, "misevi\u010diu": 12, "mix": [8, 9, 10], "mod": 3, "mode": 8, "model": [3, 4, 8, 9, 17], "model_object": [2, 3, 9], "model_training_algorithm": 8, "modelobject": [3, 8], "modi": 8, "modifi": [0, 1, 12, 17], "modu": 8, "modul": 2, "modulu": 8, "moe": 4, "moment": 5, "monaci": 4, "moptipi": [0, 1, 4, 8, 15, 17], "moptipyapp": 0, "moptipyapps_argpars": [1, 2, 3], "more": [0, 1, 4, 6, 7, 8, 10, 11, 15, 17], "most": [0, 1, 6, 11, 17], "motipyapps_footer_bottom_com": [1, 2, 3], "move": [0, 1, 5, 6, 7, 8, 9, 10, 15], "movement": 5, "mr": [0, 1, 4, 5, 6, 7, 12, 15, 17], "ms_for_train": [3, 8], "ms_per_model_run": [3, 8], "msc": [8, 9, 10], "much": [0, 1, 4, 8, 9, 11], "multi": [0, 1, 8], "multi_run_od": [3, 8], "multipl": [0, 1, 4, 5, 6, 8, 9, 11, 17], "multipli": [6, 7, 9, 11, 12, 17], "murga": 15, "must": [0, 1, 5, 6, 8, 11, 17], "mut2an0iotd0mxxh": [4, 5], "mutual": [11, 17], "mv1998esottdfbpp": 4, "mxga": [3, 4], "mypi": [0, 1], "n": [0, 1, 3, 4, 5, 6, 8, 11, 12, 15, 17], "n_bin": [3, 4, 7], "n_citi": [0, 1, 3, 15, 17], "n_different_item": [0, 1, 3, 4, 6], "n_different_object": [0, 1], "n_item": [3, 4, 6, 7], "n_point": [3, 4, 6, 10, 17], "n_run": [4, 6, 8], "nair": 12, "name": [0, 1, 3, 4, 6, 7, 8, 11, 12, 13, 14, 15, 16, 17, 18], "name_bas": 8, "name_suffix": [3, 4], "name_to_str": 4, "nanj": 15, "natur": [11, 15], "navig": 10, "nciti": [15, 17], "ncities_from_tsplib_nam": [3, 15], "ndarrai": [4, 5, 6, 7, 8, 11, 12, 14, 15, 17], "ndifferentitem": 4, "nearest": [0, 1, 11, 15], "necessarili": 17, "need": [0, 1, 4, 5, 6, 7, 8, 9, 11, 17], "neg": 17, "negat": 5, "negoti": [0, 1], "neighbor": [0, 1, 11, 15], "neighborhood": [0, 1, 11, 15], "neither": [8, 12], "nemhaus": 17, "net": [0, 1, 4, 8, 11, 17], "netherland": 15, "netto": 12, "network": [8, 9], "neural": [8, 9], "neuron": 9, "never": [0, 1, 5, 6, 11, 15, 17], "nevertheless": [0, 1], "new": [5, 6, 8, 12, 15], "newest": [0, 1], "next": [0, 1, 5, 6, 7, 8, 11, 15, 17], "nextaft": 6, "nice": [9, 11], "nitem": 4, "nj": 15, "nl": 4, "no_ref": [3, 4], "noack": [8, 9, 10], "node": [0, 1, 15], "non": [0, 1, 4, 15, 17], "none": [3, 4, 5, 6, 8, 9, 11, 12, 14, 15, 17], "nonlinear": 10, "nor": [0, 1, 11, 12], "normal": [0, 1, 4, 8, 15], "note": [11, 15], "noth": [8, 11], "notic": [0, 1, 4, 8, 17], "novemb": [15, 16, 17], "now": [0, 1, 5, 6, 7, 8, 9, 11, 15], "np": [4, 6, 7, 8, 11, 12, 15, 17], "nputil": 17, "nstep": 8, "nug12": 12, "nullari": [0, 1], "numb": 6, "numba": 9, "number": [0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 17], "number_of_items_in_last_bin": 7, "numer": [9, 17], "numpi": [4, 6, 11, 17], "ny": 12, "o": [4, 5, 8, 15, 17], "o_i": [0, 1], "obei": 15, "obj": 6, "object": [0, 1, 2, 3, 4, 5, 6, 9, 11, 14, 15, 17], "objective_bound": [3, 4], "observ": [6, 8], "obtain": [4, 8, 15, 17], "obviou": [0, 1], "obvious": [4, 8, 9, 17], "occupi": [4, 5, 6, 7, 17], "occur": [0, 1, 4, 5, 17], "octob": 4, "od": [2, 3], "odd": [0, 1, 11, 17], "off": [6, 8, 11], "offer": [0, 1, 3, 4, 8, 9, 16, 17, 18], "offici": [0, 1], "offset": 7, "often": [0, 1, 4, 5, 8, 17], "oi": [0, 1], "ok": [0, 1, 11], "old": 8, "omit": 17, "on_binpacking2d": [2, 3], "on_complet": [3, 8], "on_tsp": [2, 3], "onc": [0, 1, 4, 5, 8, 11, 15, 17], "one": [0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 17], "ones": [6, 17], "ongo": 17, "onli": [0, 1, 4, 5, 6, 7, 8, 9, 11, 15, 17], "onlin": 17, "open": [0, 1, 5, 13, 15, 16, 18], "open_resource_stream": [3, 12, 13, 15, 16, 17, 18], "oper": [0, 1, 4, 5, 12, 15, 17], "oppon": 17, "oppos": 17, "opposit": 4, "opt": [12, 15], "opt_tour_from_fil": [3, 15], "opt_tour_from_resourc": [3, 15], "optim": [0, 3, 4, 5, 6, 7, 8, 9, 12, 13, 15, 17], "optima": 15, "optimum": 12, "option": [0, 1, 4, 8, 11, 12, 15, 17], "order": [4, 8, 11, 15, 17], "order1d": [0, 1, 2, 3], "order1d_3_1": 11, "order1d_3_2": 11, "order1d_4_2": 11, "order1d_4_2_2": 11, "order1d_4_3": 11, "order1d_4_3_2": 11, "orderingspac": [3, 11], "ordinari": [0, 1, 8], "org": [0, 1, 4, 5, 8, 9, 10, 12, 13, 15, 16, 17], "orig": [6, 15], "origin": [0, 1, 4, 5, 6, 8, 9, 10, 11, 12, 15, 16, 17, 18], "orsa": [15, 16], "orthogon": [4, 5], "oscil": 10, "oswaldo": 12, "other": [0, 1, 3, 5, 7, 8, 9, 11, 15, 17], "otherwis": [4, 5, 6, 8, 15, 17], "our": [0, 1, 3, 5, 6, 7, 8, 9, 15, 17, 18], "ourselv": 9, "out": [0, 1, 3, 4, 5, 8, 9, 17], "outcom": 4, "output": [0, 1, 3, 8, 9, 10], "outsid": [4, 5], "over": [0, 1, 4, 5, 7, 8, 17], "overal": [0, 1, 4, 5, 7, 11, 12], "overhead": 5, "overlap": 4, "own": [0, 1, 9, 17], "p": [0, 1, 4, 8, 9, 12, 15], "p1": 11, "p2": 11, "pac": [3, 4], "pack": [2, 3, 5, 6, 7, 14, 17], "packag": [0, 1, 2], "packing_item_str": 4, "packing_result": [2, 3], "packing_spac": [2, 3], "packing_statist": [2, 3], "packingresult": [3, 4], "packingspac": [3, 4], "packingstatist": [3, 4], "paderborn": 12, "page": [1, 4, 12, 15, 17], "pai": 11, "pair": [0, 1, 5, 6, 8, 11, 12, 17], "pano": 12, "paper": [4, 5, 10, 12, 15], "papho": 17, "parallel": 12, "param": 8, "param_dim": [3, 8], "paramet": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "parameter": [8, 9], "parameter_spac": [3, 8], "parameteriz": 8, "parametr": 8, "paranaens": 17, "pardalo": 12, "pari": [8, 9, 10], "pars": 4, "parse_row": [3, 4], "parser": 3, "part": [4, 8, 12, 15, 17, 18], "partial": 9, "partially_linear": [3, 8], "particular": [0, 1], "pass": [0, 1, 8], "past": [0, 1], "path": [0, 1, 4, 8, 15, 17], "patholog": [0, 1], "pattern": [0, 1, 8], "paulo": 12, "pavot2010ahgvaftatdbp": 4, "pc": 15, "pcb442": 15, "pcg64": 14, "pdf": [4, 15, 16, 17], "peak": [3, 8], "pedro": 15, "penal": 17, "penalti": 17, "per": [4, 5, 6, 8, 17], "perfect": [6, 11], "perform": [0, 1, 4, 6, 8, 11, 14, 17], "period": [0, 1, 8], "perm": 17, "permit": [6, 17], "permut": [0, 1, 4, 5, 11, 12, 14, 15, 17], "perspect": 11, "peter": [0, 1, 12], "phase": 8, "phi": [8, 9], "php": [17, 18], "physic": 10, "pick": [6, 14], "piec": [4, 6], "pinbal": [8, 9, 10], "pinkish": 8, "pip": [0, 1], "pitsouli": 12, "place": [0, 1, 4, 5, 11, 17], "plai": [0, 1, 8, 16, 17, 18], "plain": [9, 15], "plainli": 9, "plan": [0, 1, 17], "plan_length": [2, 3], "plane": [0, 1, 10], "pleas": [0, 1], "plot": [0, 1, 4, 8, 11], "plot_exampl": [3, 8], "plot_indic": 8, "plot_pack": [2, 3], "plot_point": [3, 8], "plu": [7, 8, 17], "plug": [8, 9, 10], "pnn": 9, "point": [6, 8, 9, 10, 15, 17], "polici": [0, 1], "polymtl": [0, 1, 12, 13], "pool": 4, "poolmanag": 4, "poor": 9, "pop": 8, "posit": [4, 6, 8, 17], "possibl": [0, 1, 4, 5, 6, 8, 9, 11, 17], "potenti": [0, 1, 5, 6, 11, 17], "power": [9, 11], "ppp": 8, "pppp": 8, "pr1002": 15, "pr76": 15, "practic": [0, 1, 7, 17], "pre": [0, 1, 4, 6, 8, 9], "precis": [0, 1, 11], "predefin": [3, 8], "prefer": [5, 7], "prefix": 4, "prescrib": [4, 8, 17], "present": [4, 8, 12, 15], "press": 15, "presum": [0, 1], "pretti": 9, "prevent": [0, 1], "previou": 17, "price": 4, "primit": 8, "princeton": 15, "principl": 17, "print": [3, 4, 6, 8, 11, 12, 15, 17], "privat": [0, 1], "probabl": [8, 9, 11, 17], "problem": [3, 4, 5, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18], "problemat": 15, "procedur": [0, 1, 5, 6, 8, 17], "proceed": [12, 15], "process": [0, 1, 4, 5, 6, 8, 9, 15, 17], "produc": [0, 1, 6, 8, 17], "product": [0, 1, 11, 12], "prof": [0, 1, 4, 5, 6, 7, 8, 10, 12, 15, 17], "program": [0, 1, 3, 4, 10, 17], "progress": 8, "project": [0, 1], "projectil": 8, "projectile2": 8, "propag": 9, "proper": [0, 1, 9, 17], "properli": [0, 1, 8, 11], "properti": 15, "propos": [6, 8], "prove": [0, 1], "provid": [0, 1, 3, 4, 5, 8, 9, 10, 11, 12, 15, 17], "public": [0, 1, 4, 17], "publish": [0, 1, 15], "pull": 8, "punnen": 15, "purpos": [0, 1, 7, 8, 9, 11, 15], "push": [0, 1, 4, 6, 7], "put": [0, 1, 5, 9, 11], "pycodestyl": [0, 1], "pydocstyl": [0, 1], "pyflak": [0, 1], "pylint": [0, 1], "pypi": [0, 1], "pyroma": [0, 1], "python": [0, 3], "q": 8, "qap": [2, 3, 11], "qap4_2992_3672": 12, "qaplib": [0, 1, 3, 12], "qapobject": [3, 12], "quadrat": [3, 8, 11, 12, 13], "qualiti": [0, 1], "qudrat": 11, "querido": 12, "question": [0, 1], "quickli": [0, 1, 8, 15], "quit": 7, "r": [0, 1, 4, 6, 8, 9, 10], "radiu": [0, 1, 11], "rainer": [12, 13], "rais": [4, 6, 9, 17], "random": [0, 1, 6, 8, 14, 15], "random_sampl": 8, "randomli": [6, 14], "rang": [0, 1, 8, 11, 17], "rank": [0, 1, 11, 15], "rankdata": 11, "rate": 8, "rather": 8, "ratio": 9, "raw": [0, 1, 8], "raymond": 15, "rd100": 15, "re": [6, 8, 9, 11], "reach": [0, 1, 6, 7, 8], "read": [4, 8, 15, 16, 17], "readabl": [0, 1], "real": [4, 6, 8, 9, 11], "realiti": 15, "realli": [0, 1, 11, 15], "reason": [0, 1, 8, 9, 15], "receiv": [0, 1, 4, 5, 6, 8, 10], "record": 4, "rectangl": [0, 1, 4, 5], "rectangular": [0, 1, 4], "recurs": 4, "red": 8, "redistribut": [0, 1], "reduc": [0, 1, 6, 7, 8, 11, 17], "redund": 17, "refer": [3, 4], "refin": [0, 1], "reflect": [0, 1, 11], "regard": [0, 1], "regardless": [0, 1, 7], "reinelt": [0, 1, 15, 16], "reinforc": [8, 9, 10], "relat": [0, 1, 4, 9, 12, 15, 17], "relationship": 11, "releas": [0, 1], "relev": [4, 7], "reload": 15, "rememb": [4, 5], "remov": [4, 8], "render": 4, "rendl": [0, 1, 12, 13], "repeat": [5, 17], "repeatedli": 4, "repetit": [0, 1, 4, 5, 6, 17], "replac": [8, 9, 17], "replic": [0, 1], "report": [0, 1, 12], "repositori": [0, 1, 17], "repr": [4, 15, 17], "repres": [0, 1, 4, 8, 9, 11, 12, 15, 17], "represent": [0, 1, 4, 6, 15, 17], "reproduct": 6, "requir": [0, 1, 4, 6, 7, 8, 11], "research": [0, 1, 4, 5, 12, 17, 18], "researchg": 17, "resid": 17, "resourc": [0, 1, 4, 12, 13, 15, 16, 17, 18], "respect": 8, "respons": 8, "rest": 8, "result": [0, 1, 4, 5, 6, 8, 11, 17, 18], "results_log": [2, 3], "results_plot": [2, 3], "resultslog": [3, 8], "resultsplot": [3, 8], "retur": 6, "return": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "retval": [6, 7, 9], "rev": 15, "rev_if_h_not_wors": [3, 15], "rev_if_not_wors": [3, 15], "revers": 15, "review": 15, "ri": 12, "ribeiro": 17, "rid": [7, 17], "right": [4, 5, 6, 8, 11, 15, 17], "ring": 8, "rinnooi": 15, "rita": 4, "rl": [0, 1, 3, 4, 15], "rnk": 11, "road": 15, "robert": 15, "roberto": 15, "robin": [0, 1, 17, 18], "robinx": [0, 1, 3, 17], "robinxv": [17, 18], "robust": 12, "rof": 6, "root": 8, "rotat": [0, 1, 4, 5, 6, 8], "rotor": 8, "round": [0, 1, 3, 15, 17, 18], "routin": [4, 9], "row": [4, 7, 8, 17], "rtype": 8, "ruff": [0, 1], "rui": [0, 1, 4, 5, 6, 7], "ruii": 10, "run": [0, 1, 3, 4, 6, 8], "run_od": [3, 8], "runtim": [0, 1], "rw_algo_selector": 4, "s0377": [4, 5], "s10589": 12, "s11590": 4, "s1997mmasfqap": 12, "sai": [0, 1, 7, 8, 9, 11, 17], "said": [8, 12], "sake": [0, 1], "salesman": [15, 16], "salesperson": [3, 14, 15, 16, 17], "same": [4, 5, 6, 7, 8, 9, 11, 15, 17], "sampl": [0, 1, 6, 8, 15], "samplestatist": 4, "sandjai": 4, "sane": 8, "saniti": 4, "santiti": 15, "satisfi": [0, 1], "sbhulai": 4, "scale": [6, 12, 15], "scenario": [8, 9], "schedul": [0, 1, 17, 18], "schienc": 12, "scholar": [4, 5], "school": [0, 1, 4, 5, 6, 7, 12, 15, 17], "sch\u00f6nberger": [0, 1], "scienc": 12, "scientif": [0, 1], "scipi": 11, "scope": [3, 4], "scope_inst": [1, 2, 3], "script": [0, 1, 3], "search": [0, 1, 4, 6, 7, 12, 15, 17], "search_spac": [3, 4, 6, 17], "search_space_for_n_and_round": [3, 17], "sebasti\u00e1n": 17, "second": [0, 1, 4, 5, 6, 8, 11, 12, 15, 17], "secur": [0, 1], "see": [0, 1, 6, 7, 8, 11, 17], "seed": 6, "sejla": 15, "select": [6, 11], "selector": [4, 6], "self": 17, "semgrep": [0, 1], "semi": 4, "sens": [7, 8, 11], "sensor": 8, "separ": [4, 8, 17], "separation_max": [3, 17], "separation_min": [3, 17], "separationmax": 17, "separationmin": 17, "septemb": 15, "sequenc": [4, 8, 11, 14], "seri": 15, "seriou": [0, 1], "serv": 8, "set": [0, 1, 3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 15, 17, 18], "set_model": [3, 8], "set_raw": [3, 8], "setup": [0, 1, 3, 4, 6, 8, 17], "setup_rls_f1": [4, 6], "setup_rls_f7": [4, 6], "setup_rs_f1": [4, 6], "seventi": 10, "sever": [0, 1, 4, 5, 8, 9, 10, 11, 12, 15, 17], "shall": [11, 15], "shanghai": 15, "shape": [4, 9, 17], "share": [1, 2, 11, 17], "shenzhen": [8, 9, 10], "shigeyoshi": 15, "ship": 4, "shmoi": 15, "short": 17, "shorter": [8, 15, 17], "shortest": [0, 1, 12, 15], "shot": [0, 1], "should": [0, 1, 6, 8, 9, 11, 15, 16, 17], "show": 15, "shown": 17, "showpdf": [4, 5], "shuffl": 6, "si175": 15, "sigma": [0, 1], "sign": [0, 1, 4, 5, 14], "signedpermut": [4, 14], "silvano": 4, "similar": [6, 7, 9], "similarli": 7, "simoni": 4, "simpl": [9, 15, 17], "simpli": [6, 8], "simplic": [0, 1], "simul": [3, 8, 9, 12, 15], "simultan": 8, "sin": 8, "sinc": [0, 1, 6, 7, 8, 9, 11, 15, 17], "singapor": 15, "singl": [0, 1, 4, 5, 7, 8, 9, 15], "singular": 4, "sio": 8, "sit": [0, 1, 11], "site": 4, "situat": [0, 1, 5, 11, 17], "six": 10, "size": [0, 1, 4, 5, 6, 7, 8, 9], "skip": [8, 17], "skip_if_exist": 8, "skylin": 7, "slack": 6, "slice": 8, "slightli": 8, "slot": [0, 1, 8, 11, 17], "slow": 8, "slower": 5, "small": [4, 7, 8, 11, 15], "smaller": [5, 6, 7, 8, 11, 15], "smallest": [0, 1, 7, 9, 17], "smallest_area": 7, "smooth": 8, "sneakili": 17, "so": [0, 1, 5, 6, 7, 8, 9, 11, 12, 15, 17], "societi": [4, 12], "softwar": [0, 1, 15, 16], "solut": [0, 1, 4, 6, 7, 8, 11, 12, 15, 17], "solution_spac": [4, 6], "solv": [0, 1, 3, 4, 6, 8, 11, 12, 15, 16, 17, 18], "some": [0, 1, 3, 6, 8, 9, 11, 15, 17, 18], "someth": [0, 1, 8, 9, 11], "sometim": [0, 1, 8], "somewher": 7, "soon": [0, 1, 8], "sophist": 9, "sort": [4, 11, 17], "sourc": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "source_url": 4, "space": [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 15, 17], "special": 3, "specif": [0, 1, 4, 9, 11], "specifi": [0, 1, 4], "speed": [5, 8], "spend": 8, "spent": 8, "split": [6, 8], "splitlin": 11, "sport": [17, 18], "springer": [4, 12, 17], "sqrt": [0, 1, 8, 10, 15], "squar": [0, 1, 4, 7, 8, 9], "sr": 4, "srinivasan": 15, "ssci": 15, "ssci51031": 15, "ssh": [0, 1], "st": [0, 1, 15], "st70": 15, "sta_dim": 8, "stabl": [0, 1, 8, 9], "stadium": 17, "stage": 17, "stai": 11, "stand": 4, "standard": [0, 1, 3, 4, 12, 15, 17], "start": [0, 1, 4, 5, 6, 8, 10, 11, 15, 17], "start0": 8, "start1": 8, "start_color": [3, 8], "start_mark": [3, 8], "starting_point": [2, 3], "starting_st": 8, "stat": 11, "state": [0, 1, 3, 8, 9, 10, 17], "state_dim": [3, 8, 9], "state_dim_mod": [3, 8], "state_dims_in_j": [3, 8], "statement": [0, 1], "static": [4, 11, 12, 15, 17], "statist": 4, "steer": [3, 8], "stefan": [12, 13], "stem": 12, "step": [0, 1, 4, 5, 6, 7, 8, 17], "still": [0, 1, 15], "stock": 4, "stop": [4, 5, 8], "store": [0, 1, 4, 8, 15, 17], "str": [3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18], "straightforward": 17, "strang": 15, "strategi": [9, 10], "streak": [0, 1, 17], "stream": [12, 13, 15, 16, 18], "street": 15, "string": [0, 1, 3, 4, 6, 11, 12, 15, 17], "stringio": 8, "strip": [0, 1], "strive": [0, 1], "strongli": 10, "strt": 8, "structur": [0, 1, 4, 6, 8, 9, 11, 17], "stu": [0, 1, 15], "stuart": [0, 1, 8, 10], "stuart_landau": [3, 8], "stuart_landau_111": [8, 10], "stuart_landau_4": [8, 10], "student": [0, 1, 4, 5, 6, 7, 12, 15, 17], "studi": 15, "stuff": [0, 1, 4], "style": [0, 1], "st\u00fctzle": 12, "sub": 8, "subclass": 8, "subject": [9, 12], "submodul": [1, 2], "subpackag": [1, 2], "subsequ": [5, 7], "subset": 15, "subtract": [8, 11], "succe": 8, "success": [0, 1, 8], "suffix": 4, "suggest": [0, 1, 5], "suggestedxin01": 11, "suilen": 4, "sullivan": 4, "sum": [0, 1, 4, 8, 11, 12, 15, 17], "sum_up_result": [3, 8], "summar": 17, "sun": [8, 9, 10], "sup_titl": 8, "superfici": 17, "supervis": [0, 1, 4, 5, 6, 7, 12, 15, 17], "support": 15, "supports_model_mod": 8, "suppos": [5, 9], "sure": [0, 1, 15, 16], "surrog": 8, "surrogate_optim": [2, 3], "surrogateoptim": [3, 8], "survei": 12, "suspect": 5, "sussex": 15, "swap": 11, "swap_dist": [3, 11], "switch": [8, 17], "switzerland": 12, "symmatr": 11, "symmetr": [11, 14, 15, 17], "symposium": 15, "synthes": [3, 8, 9], "synthesi": [8, 9], "system": [0, 1, 2, 3, 9, 12], "system_model": [2, 3], "systemmodel": [3, 8], "t": [3, 4, 8, 11, 15, 17], "t1": 11, "t1991rtsftqap": 12, "t1995coisftqap": 12, "t2": 11, "t3": 11, "t_from_od": [3, 8], "tabl": [4, 15, 17], "taboo": 12, "tabu": 12, "tackl": [0, 1, 3], "tag": [3, 11], "tag_titl": [3, 11], "tai12a": 12, "taillard": 12, "take": [0, 1, 6, 8, 9, 17], "taken": [0, 1, 9, 17], "tan": 15, "tang": 15, "tania": 12, "target": 6, "task": [0, 1, 8, 11], "tb1994aisaaftqap": 12, "team": [0, 1, 3, 17], "technic": 12, "techniqu": 4, "technischen": [8, 9, 10], "technologi": [8, 9, 10, 12], "tell": [0, 1], "temp": 7, "temp_1": 17, "temp_2": 17, "tempa": 7, "templat": 6, "temporari": [7, 17], "tend": [0, 1, 5], "teng": [4, 5], "term": [0, 1, 7], "termin": [6, 15], "test": [2, 3, 4, 8, 9, 15], "test_starting_st": [3, 8], "test_step": [3, 8], "test_tim": [3, 8], "testb": 8, "text": [4, 6, 8, 9, 11, 15, 17], "textio": [13, 16, 18], "textur": 6, "tg1997amftqap": 12, "th": [0, 1, 15], "than": [0, 1, 4, 5, 6, 8, 11, 15, 17], "the_inst": 11, "the_spac": 11, "the_str": 11, "thei": [0, 1, 4, 5, 6, 7, 8, 11], "them": [0, 1, 4, 5, 8, 11, 15, 17], "themselv": 8, "therefor": [0, 1, 5, 6, 7, 8, 12, 17], "thereof": 8, "theses": [8, 10], "thesi": [8, 9, 10], "thi": [0, 1, 3, 4, 5, 6, 7, 8, 9, 11, 12, 15, 16, 17, 18], "thing": [0, 1, 6, 8, 11, 15], "think": [0, 1, 8], "third": [0, 1, 4, 5, 9, 11, 12], "thoma": [0, 1, 4, 5, 6, 7, 12, 15, 17], "thomasweis": [0, 1, 3], "thonemann": 12, "thorough": [0, 1], "those": [0, 1, 6, 7, 8, 17], "though": [0, 1, 7, 8], "three": [0, 1, 4, 8, 10, 11], "three_coupled_oscil": [3, 8], "through": [6, 11], "throw": 6, "thu": [0, 1, 5, 8, 10, 11, 17], "tianyu": [0, 1, 15], "time": [0, 1, 4, 5, 6, 7, 8, 9, 11, 12, 15, 17], "time_of_flight": 8, "timet": [17, 18], "titl": [4, 8, 11], "to_bin_count": [4, 7], "to_compact_str": [3, 4], "to_csv": [3, 4], "to_str": [3, 4, 6, 11, 17], "to_stream": [3, 15], "togeth": [0, 1, 4, 6, 7, 8, 9, 17], "toggl": 8, "too": [0, 1, 6, 17], "took": 6, "tool": [0, 1, 3, 6, 8, 9, 10, 11, 15], "toolkit": [8, 9, 10], "top": [4, 5], "topologi": 11, "total": [0, 1, 4, 6, 7, 8, 10, 17], "total_item_area": [3, 4, 6, 7], "totaltim": 8, "tour": [0, 1, 14, 15, 17], "tour_length": [2, 3], "tour_length_lower_bound": [3, 15, 17], "tour_length_upper_bound": [3, 15], "tourlength": [3, 15], "tourlengthlowerbound": [15, 17], "tourlengthupperbound": [15, 17], "tournament": [17, 18], "toward": [4, 6, 7, 8, 17], "train": [8, 9, 10], "training_starting_st": [3, 8], "training_step": [3, 8], "training_tim": [3, 8], "trajectori": [0, 1], "transform": [4, 8, 17], "transit": 9, "translat": [0, 1, 6, 11, 17], "transport": [0, 1], "travel": [3, 14, 15, 16, 17, 18], "travel_distance_x": 8, "travelrepo": [17, 18], "treat": [6, 9], "tri": [0, 1, 4, 5, 6, 7, 8], "triangl": 15, "trick": 17, "trivedi": 15, "trivial": [0, 1, 4, 12], "trivial_bound": [3, 12], "troubl": [0, 1], "true": [3, 4, 6, 7, 8, 9, 14, 15, 17], "try": [0, 1, 3, 4, 5, 6, 7, 8, 9, 11, 15, 17], "tryceratop": [0, 1], "tsp": [2, 3, 14, 18], "tsp225": 15, "tsp95": [15, 16], "tsp_instances_for_test": [3, 14], "tspea1p1revn": [3, 15], "tspfaq": [15, 16], "tspfea1p1revn": [3, 15], "tsplib": [0, 1, 3, 15], "tsplib95": [15, 16], "tsutsui": 15, "tt": 8, "ttime": 8, "ttp": [2, 3], "tunnel": [8, 9], "tupl": [4, 6, 8, 9, 11, 12, 15, 17], "turn": [0, 1, 6, 9, 11], "tweis": [0, 1], "twice": [0, 1, 17], "two": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 17], "txt": 4, "type": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "typeerror": [4, 6, 17], "typevar": [8, 11], "u": [4, 6, 7, 8, 9, 10, 11, 17], "ugent": [17, 18], "uk": 15, "ulrich": 12, "ulysses16": 15, "ulysses22": 15, "unari": 15, "unboundedli": 8, "under": [0, 1, 4, 5, 6, 7, 9, 12, 15, 17], "understand": [0, 1], "understood": 17, "unfortun": [0, 1, 8], "uni": [15, 16], "unibo": 4, "uniform": 17, "unimport": [0, 1], "unind": [8, 9], "uniniti": [4, 17], "union": [4, 8, 11, 14], "uniqu": [6, 11], "unit": [3, 6, 11, 14, 17], "univers": [0, 1, 4, 5, 6, 7, 12, 15, 17, 18], "universit\u00e4t": [8, 9, 10, 15, 16], "universit\u00e4tsbibliothek": [8, 9, 10], "unless": 11, "unnecessari": [0, 1], "unord": 6, "unpackag": 4, "unsaf": [0, 1], "unstructur": [0, 1, 11], "unsuccessfulli": 5, "until": [5, 8], "unus": [0, 1], "up": [0, 1, 4, 5, 6, 7, 8, 15, 17], "updat": [8, 12, 13], "upload": 17, "upper": [6, 7, 11, 12, 15, 17], "upper_bound": [3, 4, 6, 7, 12, 15, 17], "upper_bound_range_multipli": 15, "upperbound": 4, "url": 4, "urllib3": 4, "urrutia": 17, "us": [0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "usa": [12, 15], "usabl": [0, 1, 7], "use_state_dim": 8, "useabl": 7, "user": 6, "ustc": [0, 1], "usual": [0, 1, 8, 15, 17], "util": [0, 1, 4, 15, 17], "utmost": [0, 1], "v": [4, 8, 17], "v_x": 8, "v_y": 8, "valid": [0, 1, 3, 4, 6, 14, 15, 17], "validate_algorithm_on_1_2dbinpack": [3, 14], "validate_algorithm_on_1_tsp": [3, 14], "validate_algorithm_on_2dbinpack": [3, 14], "validate_algorithm_on_tsp": [3, 14], "validate_objective_on_1_2dbinpack": [3, 14], "validate_objective_on_1_tsp": [3, 14], "validate_objective_on_2dbinpack": [3, 14], "validate_objective_on_tsp": [3, 14], "validate_signed_permutation_encoding_on_1_2dbinpack": [3, 14], "validate_signed_permutation_encoding_on_2dbinpack": [3, 14], "valu": [0, 1, 4, 5, 6, 7, 8, 9, 11, 12, 15, 17], "valueerror": [4, 6, 17], "val\u00e9rio": 4, "van": [0, 1, 4, 15, 17, 18], "variabl": [0, 1, 3, 6, 8, 9, 11], "variant": [0, 1, 5, 8, 17], "variat": 15, "varieti": [0, 1], "variou": [0, 1, 8], "va\u0161ek": 15, "vdbbmsb2016asiasstfi": 4, "vector": [6, 8, 9], "vectorspac": [6, 8], "venic": 4, "ver": 3, "veri": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17], "verifi": [4, 8], "verlag": 4, "versa": [0, 1, 6, 11, 17], "version": [0, 1, 2, 11, 14, 15], "vertic": 6, "via": [0, 1, 4, 8, 9, 12, 15, 17], "vice": [0, 1, 6, 11, 17], "vin\u00edciu": 4, "violat": [6, 15, 17], "violet": 8, "visit": [0, 1, 12, 13, 15, 17], "visual": [0, 1, 11], "vnd": 4, "vol": [8, 9, 10], "volum": 15, "vu": 4, "vulner": [0, 1], "vultur": [0, 1], "w": 8, "wa": [4, 5, 6, 8, 17], "wai": [0, 1, 4, 6, 8, 9, 11, 15, 17], "wake": [8, 9, 10], "wang": 4, "want": [0, 1, 4, 6, 8, 9, 11, 15], "warm": 8, "warmup": 8, "warmup_algorithm": 8, "warranti": [0, 1], "warszawa": 10, "wast": [0, 1, 6], "we": [0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 17, 18], "websit": [12, 13], "wei": [8, 9, 10], "weight": [0, 1, 8, 9, 11, 15], "weis": [0, 1, 4, 5, 6, 7, 12, 15, 17], "well": [0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 15, 17], "wenxiang": 15, "were": [8, 10, 11], "west": 15, "what": [0, 1, 8, 11, 15], "whatev": [8, 11], "when": [0, 1, 5, 8, 15, 17], "where": [0, 1, 4, 6, 7, 8, 9, 11, 15, 17], "wherea": [0, 1, 7, 11], "whether": [0, 1, 4, 6, 8, 11, 15], "which": [0, 1, 4, 5, 6, 7, 8, 9, 11, 12, 15, 17], "while": [0, 1, 5, 6, 8], "who": [0, 1], "whole": [6, 15], "whose": [0, 1, 6, 7, 9, 10], "why": [0, 1], "wide": [0, 1], "width": [0, 1, 4, 5, 6, 7], "wilei": 15, "wilhelm": 12, "william": 15, "wind": [8, 9], "wise": [0, 1], "with_rot": 4, "within": [0, 1, 5, 15, 17], "without": [0, 1, 4, 6, 10, 17], "without_rot": 4, "wolkowicz": 12, "won": 4, "wonder": [0, 1], "word": [0, 1, 5, 7, 8, 9, 11, 15, 17], "work": [0, 1, 4, 5, 6, 7, 8, 9, 10, 12, 15, 17], "workshop": [0, 1, 12], "world": [4, 8, 9], "wors": [0, 1, 5, 15], "worst": 5, "would": [0, 1, 4, 5, 6, 7, 8, 9, 11, 15, 17], "wp": 17, "wrap": [6, 8, 15], "write": [4, 8, 9, 15, 17], "writeln": [8, 9], "writer": 4, "written": [4, 9], "wrong": [4, 6, 17], "wu": 15, "wuhan": 15, "www": [0, 1, 4, 5, 17], "x": [0, 1, 4, 5, 6, 7, 8, 11, 12, 15, 17], "x1": [4, 6, 11, 17], "x2": [4, 6, 11, 17], "x4": 11, "xi": 15, "xiang": 17, "xin": 15, "xml": [17, 18], "xmlc": [8, 9, 10], "xx": [4, 8], "xxx": 12, "x\u00b2": [0, 1], "y": [0, 1, 4, 5, 6, 7, 8, 14, 15, 17], "y1": [4, 17], "y2": [4, 17], "yao": 15, "ye": 5, "year": [0, 1, 10], "yellow": 8, "yet": [0, 1, 4, 11, 15, 17], "yield": [5, 6], "york": 12, "yoslan": [8, 9, 10], "you": [0, 1, 3, 4, 8, 11, 14, 15, 16, 17, 18], "your": [0, 1, 14], "yuxiang": [8, 9, 10], "yy": 17, "y\u00b2": [0, 1], "z": [6, 7, 8, 9, 15], "zbigniew": 15, "zero": [6, 11, 17], "zhao": [0, 1, 4, 5, 6, 7], "zhize": 15, "zhu": 12, "zip": 4, "zr1329142665": [0, 1, 4, 5, 6, 7], "\u00e7ela": [0, 1, 12], "\u00e9ric": 12, "\u4e0a\u6d77": 15, "\u4e2d\u56fd\u5b89\u5fbd\u7701\u5408\u80a5\u5e02": [0, 1, 4, 5, 6, 7, 12, 15, 17], "\u4eba\u5de5\u667a\u80fd\u4e0e\u5927\u6570\u636e\u5b66\u9662": [0, 1, 4, 5, 6, 7, 12, 15, 17], "\u5317\u4eac": 15, "\u5357\u4eac": 15, "\u5408\u80a5": 15, "\u5408\u80a5\u5927\u5b66": [0, 1, 4, 5, 6, 7, 12, 15, 17], "\u5408\u80a5\u5b66\u9662": [0, 1], "\u54c8\u5c14\u6ee8": 15, "\u54c8\u5c14\u6ee8\u5de5\u4e1a\u5927\u5b66": [8, 9, 10], "\u57fa\u4e8e\u6df1\u5ea6\u5f3a\u5316\u5b66\u4e60\u65b9\u6cd5\u7684": [8, 9, 10], "\u57fa\u4e8e\u6df1\u5ea6\u5f3a\u5316\u5b66\u4e60\u7684\u5c04\u6d41\u6df7\u5408\u589e\u5f3a\u63a7\u5236": [8, 9, 10], "\u5b59\u4f1f": [8, 9, 10], "\u5e94\u7528\u4f18\u5316\u7814\u7a76\u6240": [0, 1, 4, 5, 6, 7, 12, 15, 17], "\u6606\u660e": 15, "\u66f9\u7fd4": 17, "\u674e\u5b87\u7fd4": [8, 9, 10], "\u6881\u5929\u5b87": [0, 1, 15], "\u6b66\u6c49": 15, "\u6c64\u536b\u601d\u6559\u6388": [0, 1, 4, 5, 6, 7, 12, 15, 17], "\u6d41\u4f53\u5f39\u7403\u5c3e\u6d41\u63a7\u5236": [8, 9, 10], "\u6df1\u5733": [8, 9, 10], "\u897f\u5b89": 15, "\u8d75\u777f": [0, 1, 4, 5, 6, 7], "\u91cd\u5e86": 15, "\u957f\u6c99": 15, "\u9648\u5609\u9633": 12, "\u9999\u6e2f": 15}, "titles": ["1. Introduction", "moptipyapps: Applications of Metaheuristic Optimization in Python", "moptipyapps", "moptipyapps package", "moptipyapps.binpacking2d package", "moptipyapps.binpacking2d.encodings package", "moptipyapps.binpacking2d.instgen package", "moptipyapps.binpacking2d.objectives package", "moptipyapps.dynamic_control package", "moptipyapps.dynamic_control.controllers package", "moptipyapps.dynamic_control.systems package", "moptipyapps.order1d package", "moptipyapps.qap package", "moptipyapps.qap.qaplib package", "moptipyapps.tests package", "moptipyapps.tsp package", "moptipyapps.tsp.tsplib package", "moptipyapps.ttp package", "moptipyapps.ttp.robinx package"], "titleterms": {"1": [0, 1], "2": [0, 1], "3": [0, 1], "4": [0, 1], "5": [0, 1], "6": [0, 1], "7": 1, "One": [0, 1], "The": [0, 1], "analysi": [0, 1], "ann": 9, "applic": [0, 1], "assign": [0, 1], "bin": [0, 1], "bin_count": 7, "bin_count_and_empti": 7, "bin_count_and_last_empti": 7, "bin_count_and_last_skylin": 7, "bin_count_and_last_smal": 7, "bin_count_and_lowest_skylin": 7, "bin_count_and_smal": 7, "binpacking2d": [4, 5, 6, 7], "bk": 4, "code": 1, "codegen": 9, "contact": [0, 1], "control": [0, 1, 8, 9], "cubic": 9, "dimension": [0, 1], "distanc": 11, "dynam": [0, 1], "dynamic_control": [8, 9, 10], "ea1p1_revn": 15, "encod": 5, "error": [6, 17], "errors_and_hard": 6, "except": [0, 1], "experi": [4, 6], "experiment_raw": 8, "experiment_surrog": 8, "fea1p1_revn": 15, "game_encod": 17, "game_plan": 17, "game_plan_spac": 17, "hard": 6, "ibl_encoding_1": 5, "ibl_encoding_2": 5, "inst_decod": 6, "instal": [0, 1], "instanc": [4, 8, 11, 12, 15, 17], "instance_spac": 6, "instgen": 6, "introduct": [0, 1], "known_optima": 15, "licens": [0, 1], "linear": 9, "lorenz": 10, "make_inst": 4, "metaheurist": 1, "min_ann": 9, "model_object": 8, "modul": [1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 17], "moptipyapp": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "object": [7, 8, 12], "od": 8, "on_binpacking2d": 14, "on_tsp": 14, "optim": 1, "order": [0, 1], "order1d": 11, "pack": [0, 1, 4], "packag": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], "packing_result": 4, "packing_spac": 4, "packing_statist": 4, "partially_linear": 9, "peak": 9, "plan_length": 17, "plot_pack": 4, "predefin": 9, "problem": [0, 1, 6], "python": 1, "qap": [0, 1, 12, 13], "qaplib": 13, "quadrat": [0, 1, 9], "results_log": 8, "results_plot": 8, "robinx": 18, "salesperson": [0, 1], "share": 3, "space": 11, "starting_point": 8, "static": [0, 1], "stuart_landau": 10, "submodul": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 17], "subpackag": [3, 4, 8, 12, 15, 17], "surrogate_optim": 8, "synthesi": [0, 1], "system": [8, 10], "system_model": 8, "test": [0, 1, 14], "three_coupled_oscil": 10, "tour_length": 15, "tournament": [0, 1], "travel": [0, 1], "tsp": [0, 1, 15, 16], "tsplib": 16, "ttp": [0, 1, 17, 18], "two": [0, 1], "unit": [0, 1], "version": 3}}) \ No newline at end of file diff --git a/setup_cfg.html b/setup_cfg.html new file mode 100644 index 00000000..38632081 --- /dev/null +++ b/setup_cfg.html @@ -0,0 +1,76 @@ +

[metadata]
+name = moptipyapps
+version = attr: moptipyapps.version.__version__
+description = Applications of Metaheuristic Optimization in Python.
+long_description = file: README.md
+long_description_content_type = text/markdown
+keywords =
+    bin packing
+license = GPL 3.0
+license_files = file: LICENSE
+classifiers =
+    Development Status :: 4 - Beta
+    Framework :: Matplotlib
+    Intended Audience :: Developers
+    Intended Audience :: Education
+    Intended Audience :: Science/Research
+    License :: OSI Approved :: GNU General Public License v3 (GPLv3)
+    Natural Language :: English
+    Natural Language :: German
+    Natural Language :: Chinese (Simplified)
+    Operating System :: Microsoft :: Windows
+    Operating System :: POSIX :: Linux
+    Programming Language :: Python :: 3 :: Only
+    Programming Language :: Python :: 3.12
+    Topic :: Education
+    Topic :: Scientific/Engineering :: Artificial Intelligence
+    Topic :: Scientific/Engineering :: Mathematics
+    Topic :: Scientific/Engineering :: Visualization
+url = https://thomasweise.github.io/moptipyapps
+author = Thomas Weise
+author_email = tweise@ustc.edu.cn
+maintainer = Thomas Weise
+maintainer_email = tweise@ustc.edu.cn
+project_urls =
+    Documentation = https://thomasweise.github.io/moptipyapps
+    Source = https://github.com/thomasWeise/moptipyapps/
+    Tracker = https://github.com/thomasWeise/moptipyapps/issues
+
+[options]
+include_package_data = True
+install_requires =
+    certifi >= 2024.8.30
+    defusedxml >= 0.7.1
+    moptipy >= 0.9.136
+    numpy < 2
+    numba >= 0.60.0
+    matplotlib >= 3.9.2
+    pycommons >= 0.8.58
+    scipy >= 1.14.1
+    urllib3 >= 2.2.3
+packages = find:
+python_requires = >= 3.12
+zip_safe = False
+
+[options.package_data]
+moptipyapps = py.typed
+moptipyapps.binpacking2d = *.txt, *.bib
+moptipyapps.qap.qaplib = *.dat
+moptipyapps.tsp.tsplib = *.tsp, *.atsp, *.opt.tour
+moptipyapps.ttp.robinx = *.xml
+
+[options.packages.find]
+exclude =
+    .coverage*
+    .github*
+    .mypy_cache*
+    .pytest_cache*
+    .ruff_cache*
+    dist*
+    docs*
+    examples*
+    moptipy.egg-info*
+    moptipyapps.egg-info*
+    tests*
+
diff --git a/setup_py.html b/setup_py.html new file mode 100644 index 00000000..b1289888 --- /dev/null +++ b/setup_py.html @@ -0,0 +1,51 @@ +

"""The setup and installation script."""
+
+from re import compile as re_compile
+from re import sub as re_sub
+from typing import Final, Pattern
+
+from setuptools import setup
+
+# We want to use our README.md as project description.
+# However, we must fix all the references inside.
+with open("README.md", encoding="utf-8-sig") as reader:
+    old_lines = reader.readlines()
+
+# It seems that the markdown parser does not auto-generate anchors. This means
+# that we need to fix all references following the pattern `[xxx](#12-hello)`
+# to `[xxx]({docu_url#hello)`, where `docu_url` is the url of our
+# documentation. We do this with a regular expression `regex_search`.
+new_lines: Final[list[str]] = []
+in_code: bool = False  # we only process non-code lines
+
+# the base url where the documentation will land
+doc_url: Final[str] = "https://thomasweise.github.io/moptipyapps"
+# the url of our repository
+repo_url: Final[str] = "https://github.com/thomasWeise/moptipyapps"
+
+# detects strings of the form [xyz](#123-bla) and gives \1=xyz and \2=bla
+regex_search: Final[Pattern] = re_compile("(\\[.+?])\\(#\\d+-(.+?)\\)")
+regex_repl: Final[str] = f"\\1({doc_url}#\\2)"
+
+# other replacements
+license_old: Final[str] = f"{repo_url}/blob/main/LICENSE"
+license_new: Final[str] = f"{doc_url}/LICENSE.html"
+
+for full_line in old_lines:
+    line: str = str.rstrip(full_line)
+    if in_code:
+        if line.startswith("```"):
+            in_code = False  # toggle to non-code
+    elif line.startswith("```"):
+        in_code = True  # toggle to code
+    else:  # fix all internal urls
+        # replace links of the form "#12-bla" to "#bla"
+        line = re_sub(regex_search, regex_repl, line)
+        line = str.replace(line, license_old, license_new)
+    new_lines.append(line)
+
+# Now we can use the code in the setup.
+setup(long_description="\n".join(new_lines),
+      long_description_content_type="text/markdown")
+
diff --git a/tc/.nojekyll b/tc/.nojekyll new file mode 100755 index 00000000..e69de29b diff --git a/tc/badge.svg b/tc/badge.svg new file mode 100644 index 00000000..c97e5048 --- /dev/null +++ b/tc/badge.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + coverage + coverage + 66% + 66% + + diff --git a/tc/index.html b/tc/index.html new file mode 100644 index 00000000..231d5056 --- /dev/null +++ b/tc/index.html @@ -0,0 +1,706 @@ + + + + + Coverage report + + + + + +
+
+

Coverage report: + 66% +

+ +
+ +
+ + +
+
+

+ Files + Functions + Classes +

+

+ coverage.py v7.6.5, + created at 2024-11-15 11:57 +0000 +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Filestatementsmissingexcludedcoverage
moptipyapps/__init__.py72071%
moptipyapps/binpacking2d/__init__.py000100%
moptipyapps/binpacking2d/bks.py359185048%
moptipyapps/binpacking2d/encodings/__init__.py000100%
moptipyapps/binpacking2d/encodings/ibl_encoding_1.py8760031%
moptipyapps/binpacking2d/encodings/ibl_encoding_2.py10071029%
moptipyapps/binpacking2d/experiment.py4623050%
moptipyapps/binpacking2d/instance.py33419094%
moptipyapps/binpacking2d/instgen/__init__.py000100%
moptipyapps/binpacking2d/instgen/errors.py874095%
moptipyapps/binpacking2d/instgen/errors_and_hardness.py2900100%
moptipyapps/binpacking2d/instgen/experiment.py269065%
moptipyapps/binpacking2d/instgen/hardness.py895094%
moptipyapps/binpacking2d/instgen/inst_decoding.py1038092%
moptipyapps/binpacking2d/instgen/instance_space.py5923061%
moptipyapps/binpacking2d/instgen/problem.py151093%
moptipyapps/binpacking2d/make_instances.py10329072%
moptipyapps/binpacking2d/objectives/__init__.py000100%
moptipyapps/binpacking2d/objectives/bin_count.py241096%
moptipyapps/binpacking2d/objectives/bin_count_and_empty.py248067%
moptipyapps/binpacking2d/objectives/bin_count_and_last_empty.py3011063%
moptipyapps/binpacking2d/objectives/bin_count_and_last_skyline.py4124041%
moptipyapps/binpacking2d/objectives/bin_count_and_last_small.py4414068%
moptipyapps/binpacking2d/objectives/bin_count_and_lowest_skyline.py4326040%
moptipyapps/binpacking2d/objectives/bin_count_and_small.py248067%
moptipyapps/binpacking2d/packing.py7911086%
moptipyapps/binpacking2d/packing_result.py29039087%
moptipyapps/binpacking2d/packing_space.py10011089%
moptipyapps/binpacking2d/packing_statistics.py28852082%
moptipyapps/binpacking2d/plot_packing.py856093%
moptipyapps/dynamic_control/__init__.py000100%
moptipyapps/dynamic_control/controller.py333091%
moptipyapps/dynamic_control/controllers/__init__.py000100%
moptipyapps/dynamic_control/controllers/ann.py621098%
moptipyapps/dynamic_control/controllers/codegen.py498084%
moptipyapps/dynamic_control/controllers/cubic.py3014053%
moptipyapps/dynamic_control/controllers/linear.py204080%
moptipyapps/dynamic_control/controllers/min_ann.py47344805%
moptipyapps/dynamic_control/controllers/partially_linear.py9471024%
moptipyapps/dynamic_control/controllers/peaks.py4419057%
moptipyapps/dynamic_control/controllers/predefined.py2912059%
moptipyapps/dynamic_control/controllers/quadratic.py259064%
moptipyapps/dynamic_control/experiment_raw.py6120067%
moptipyapps/dynamic_control/experiment_surrogate.py8447044%
moptipyapps/dynamic_control/instance.py4410077%
moptipyapps/dynamic_control/model_objective.py4000100%
moptipyapps/dynamic_control/objective.py1046094%
moptipyapps/dynamic_control/ode.py16040075%
moptipyapps/dynamic_control/results_log.py464091%
moptipyapps/dynamic_control/results_plot.py159130018%
moptipyapps/dynamic_control/starting_points.py7644042%
moptipyapps/dynamic_control/surrogate_optimizer.py18943077%
moptipyapps/dynamic_control/system.py15184044%
moptipyapps/dynamic_control/system_model.py213086%
moptipyapps/dynamic_control/systems/__init__.py000100%
moptipyapps/dynamic_control/systems/lorenz.py236074%
moptipyapps/dynamic_control/systems/stuart_landau.py213086%
moptipyapps/dynamic_control/systems/three_coupled_oscillators.py4825048%
moptipyapps/order1d/__init__.py000100%
moptipyapps/order1d/distances.py1913032%
moptipyapps/order1d/instance.py11319083%
moptipyapps/order1d/space.py351097%
moptipyapps/qap/__init__.py000100%
moptipyapps/qap/instance.py13132076%
moptipyapps/qap/objective.py3312064%
moptipyapps/qap/qaplib/__init__.py500100%
moptipyapps/shared.py1300100%
moptipyapps/tests/__init__.py000100%
moptipyapps/tests/on_binpacking2d.py15442073%
moptipyapps/tests/on_tsp.py9836063%
moptipyapps/tsp/__init__.py000100%
moptipyapps/tsp/ea1p1_revn.py5712079%
moptipyapps/tsp/fea1p1_revn.py6918074%
moptipyapps/tsp/instance.py43171084%
moptipyapps/tsp/known_optima.py4811077%
moptipyapps/tsp/tour_length.py337079%
moptipyapps/tsp/tsplib/__init__.py500100%
moptipyapps/ttp/__init__.py000100%
moptipyapps/ttp/errors.py11093015%
moptipyapps/ttp/game_encoding.py4814071%
moptipyapps/ttp/game_plan.py453093%
moptipyapps/ttp/game_plan_space.py6110084%
moptipyapps/ttp/instance.py20239081%
moptipyapps/ttp/plan_length.py5225052%
moptipyapps/ttp/robinx/__init__.py500100%
moptipyapps/version.py200100%
Total63712162066%
+

+ No items found using the specified filter. +

+
+ + + diff --git a/tc/status.json b/tc/status.json new file mode 100644 index 00000000..8cfd2c68 --- /dev/null +++ b/tc/status.json @@ -0,0 +1 @@ +{"note":"This file is an internal implementation detail to speed up HTML report generation. Its format can change at any time. You might be looking for the JSON report: https://coverage.rtfd.io/cmd.html#cmd-json","format":5,"version":"7.6.5","globals":"374247f827121a7662114fac17cafb61","files":{"z_26fd35bf8bb8d301___init___py":{"hash":"2e06ea36e441caa1d0f7797e0573561a","index":{"url":"z_26fd35bf8bb8d301___init___py.html","file":"moptipyapps/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":7,"n_excluded":0,"n_missing":2,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e5221e4e8fc15630___init___py":{"hash":"2a630f0db43a28b4bd4c0bfbb1f71e46","index":{"url":"z_e5221e4e8fc15630___init___py.html","file":"moptipyapps/binpacking2d/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e5221e4e8fc15630_bks_py":{"hash":"a0e52e2fe647e3dddacdad04bf77850b","index":{"url":"z_e5221e4e8fc15630_bks_py.html","file":"moptipyapps/binpacking2d/bks.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":359,"n_excluded":0,"n_missing":185,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_8c9c75e0af0f68db___init___py":{"hash":"be026421589c5441c96245c3b44e271e","index":{"url":"z_8c9c75e0af0f68db___init___py.html","file":"moptipyapps/binpacking2d/encodings/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_8c9c75e0af0f68db_ibl_encoding_1_py":{"hash":"7fe9edaa3abea3b05685ea2b3e341f21","index":{"url":"z_8c9c75e0af0f68db_ibl_encoding_1_py.html","file":"moptipyapps/binpacking2d/encodings/ibl_encoding_1.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":87,"n_excluded":0,"n_missing":60,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_8c9c75e0af0f68db_ibl_encoding_2_py":{"hash":"8a136da5d998ca46b975c7e01d1f6bdf","index":{"url":"z_8c9c75e0af0f68db_ibl_encoding_2_py.html","file":"moptipyapps/binpacking2d/encodings/ibl_encoding_2.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":100,"n_excluded":0,"n_missing":71,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e5221e4e8fc15630_experiment_py":{"hash":"e2f156224d2c997b2cfbdafff58a836f","index":{"url":"z_e5221e4e8fc15630_experiment_py.html","file":"moptipyapps/binpacking2d/experiment.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":46,"n_excluded":0,"n_missing":23,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e5221e4e8fc15630_instance_py":{"hash":"3b59c6872ffd4a2c6138e8141fe1e6b8","index":{"url":"z_e5221e4e8fc15630_instance_py.html","file":"moptipyapps/binpacking2d/instance.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":334,"n_excluded":0,"n_missing":19,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_872606d257725619___init___py":{"hash":"42cb5dfab8152ef3f0726cb531215cb0","index":{"url":"z_872606d257725619___init___py.html","file":"moptipyapps/binpacking2d/instgen/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_872606d257725619_errors_py":{"hash":"4d7e3d0e5641f23e6252812bfa1b332b","index":{"url":"z_872606d257725619_errors_py.html","file":"moptipyapps/binpacking2d/instgen/errors.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":87,"n_excluded":0,"n_missing":4,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_872606d257725619_errors_and_hardness_py":{"hash":"2f75472c912d80ecd30168c892833063","index":{"url":"z_872606d257725619_errors_and_hardness_py.html","file":"moptipyapps/binpacking2d/instgen/errors_and_hardness.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":29,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_872606d257725619_experiment_py":{"hash":"92eec0ff91ae0ec9064737f8a380e4f1","index":{"url":"z_872606d257725619_experiment_py.html","file":"moptipyapps/binpacking2d/instgen/experiment.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":26,"n_excluded":0,"n_missing":9,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_872606d257725619_hardness_py":{"hash":"0ac86bfd8b4ba10a787cc1940223960c","index":{"url":"z_872606d257725619_hardness_py.html","file":"moptipyapps/binpacking2d/instgen/hardness.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":89,"n_excluded":0,"n_missing":5,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_872606d257725619_inst_decoding_py":{"hash":"7cd2c2e75e375f2395cf7eeab44eae8c","index":{"url":"z_872606d257725619_inst_decoding_py.html","file":"moptipyapps/binpacking2d/instgen/inst_decoding.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":103,"n_excluded":0,"n_missing":8,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_872606d257725619_instance_space_py":{"hash":"fa7b89205de5606728f974d3d02d343b","index":{"url":"z_872606d257725619_instance_space_py.html","file":"moptipyapps/binpacking2d/instgen/instance_space.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":59,"n_excluded":0,"n_missing":23,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_872606d257725619_problem_py":{"hash":"d72fa4ab7273aab63ced2fe3a381a752","index":{"url":"z_872606d257725619_problem_py.html","file":"moptipyapps/binpacking2d/instgen/problem.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":15,"n_excluded":0,"n_missing":1,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e5221e4e8fc15630_make_instances_py":{"hash":"15964b5962d187e044b20c22fe4a3a5d","index":{"url":"z_e5221e4e8fc15630_make_instances_py.html","file":"moptipyapps/binpacking2d/make_instances.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":103,"n_excluded":0,"n_missing":29,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e9ccd1ed09c1c0ed___init___py":{"hash":"7c10ee190f8e0e4a2ac03ee0e35bbb9e","index":{"url":"z_e9ccd1ed09c1c0ed___init___py.html","file":"moptipyapps/binpacking2d/objectives/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e9ccd1ed09c1c0ed_bin_count_py":{"hash":"913c76a0634d85e07811d98072cf2a5f","index":{"url":"z_e9ccd1ed09c1c0ed_bin_count_py.html","file":"moptipyapps/binpacking2d/objectives/bin_count.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":24,"n_excluded":0,"n_missing":1,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e9ccd1ed09c1c0ed_bin_count_and_empty_py":{"hash":"7cc79870394244d5908b45b1996bf7fd","index":{"url":"z_e9ccd1ed09c1c0ed_bin_count_and_empty_py.html","file":"moptipyapps/binpacking2d/objectives/bin_count_and_empty.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":24,"n_excluded":0,"n_missing":8,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e9ccd1ed09c1c0ed_bin_count_and_last_empty_py":{"hash":"89c1501d8f178ae3678933bcba676955","index":{"url":"z_e9ccd1ed09c1c0ed_bin_count_and_last_empty_py.html","file":"moptipyapps/binpacking2d/objectives/bin_count_and_last_empty.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":30,"n_excluded":0,"n_missing":11,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e9ccd1ed09c1c0ed_bin_count_and_last_skyline_py":{"hash":"c5cdec5b08a3dbe1397beb206a8ceae0","index":{"url":"z_e9ccd1ed09c1c0ed_bin_count_and_last_skyline_py.html","file":"moptipyapps/binpacking2d/objectives/bin_count_and_last_skyline.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":41,"n_excluded":0,"n_missing":24,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e9ccd1ed09c1c0ed_bin_count_and_last_small_py":{"hash":"135b07de3ea966e862104c6b32037473","index":{"url":"z_e9ccd1ed09c1c0ed_bin_count_and_last_small_py.html","file":"moptipyapps/binpacking2d/objectives/bin_count_and_last_small.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":44,"n_excluded":0,"n_missing":14,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e9ccd1ed09c1c0ed_bin_count_and_lowest_skyline_py":{"hash":"b891a8b6785ac349f6ce08f28fb5597f","index":{"url":"z_e9ccd1ed09c1c0ed_bin_count_and_lowest_skyline_py.html","file":"moptipyapps/binpacking2d/objectives/bin_count_and_lowest_skyline.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":43,"n_excluded":0,"n_missing":26,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e9ccd1ed09c1c0ed_bin_count_and_small_py":{"hash":"9e8f0513e53ef6c184998345eb12921e","index":{"url":"z_e9ccd1ed09c1c0ed_bin_count_and_small_py.html","file":"moptipyapps/binpacking2d/objectives/bin_count_and_small.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":24,"n_excluded":0,"n_missing":8,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e5221e4e8fc15630_packing_py":{"hash":"b6786b80e10b5c0858632482aee0f1e6","index":{"url":"z_e5221e4e8fc15630_packing_py.html","file":"moptipyapps/binpacking2d/packing.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":79,"n_excluded":0,"n_missing":11,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e5221e4e8fc15630_packing_result_py":{"hash":"f72d08ad64dcdddecb77edea21a52075","index":{"url":"z_e5221e4e8fc15630_packing_result_py.html","file":"moptipyapps/binpacking2d/packing_result.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":290,"n_excluded":0,"n_missing":39,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e5221e4e8fc15630_packing_space_py":{"hash":"47baaa41a15cd9beecf5008eeed8b430","index":{"url":"z_e5221e4e8fc15630_packing_space_py.html","file":"moptipyapps/binpacking2d/packing_space.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":100,"n_excluded":0,"n_missing":11,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e5221e4e8fc15630_packing_statistics_py":{"hash":"9cea1f247e40041843cdf846c5ea1305","index":{"url":"z_e5221e4e8fc15630_packing_statistics_py.html","file":"moptipyapps/binpacking2d/packing_statistics.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":288,"n_excluded":0,"n_missing":52,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_e5221e4e8fc15630_plot_packing_py":{"hash":"cee8cb4800010781f91174f3a93833d6","index":{"url":"z_e5221e4e8fc15630_plot_packing_py.html","file":"moptipyapps/binpacking2d/plot_packing.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":85,"n_excluded":0,"n_missing":6,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f___init___py":{"hash":"56183b8dae0552ec7ecaeb5e6212031f","index":{"url":"z_1200a9633299360f___init___py.html","file":"moptipyapps/dynamic_control/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f_controller_py":{"hash":"39e2381b869f169eb819f785b31abb5d","index":{"url":"z_1200a9633299360f_controller_py.html","file":"moptipyapps/dynamic_control/controller.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":33,"n_excluded":0,"n_missing":3,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_b3d87d810e34754a___init___py":{"hash":"e15273490fe19f4697994bbf8c9673bb","index":{"url":"z_b3d87d810e34754a___init___py.html","file":"moptipyapps/dynamic_control/controllers/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_b3d87d810e34754a_ann_py":{"hash":"9f3315517dcf3e11c017cdc9f5b0ac3c","index":{"url":"z_b3d87d810e34754a_ann_py.html","file":"moptipyapps/dynamic_control/controllers/ann.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":62,"n_excluded":0,"n_missing":1,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_b3d87d810e34754a_codegen_py":{"hash":"0ff63b28a7ae6e83d5feff2e71e38c96","index":{"url":"z_b3d87d810e34754a_codegen_py.html","file":"moptipyapps/dynamic_control/controllers/codegen.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":49,"n_excluded":0,"n_missing":8,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_b3d87d810e34754a_cubic_py":{"hash":"629ac5e068be8964a3e2f3764ac47905","index":{"url":"z_b3d87d810e34754a_cubic_py.html","file":"moptipyapps/dynamic_control/controllers/cubic.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":30,"n_excluded":0,"n_missing":14,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_b3d87d810e34754a_linear_py":{"hash":"a28a3985f8a96ba854b3a6c7ccad56d1","index":{"url":"z_b3d87d810e34754a_linear_py.html","file":"moptipyapps/dynamic_control/controllers/linear.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":20,"n_excluded":0,"n_missing":4,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_b3d87d810e34754a_min_ann_py":{"hash":"9500c8315469aca6777865055cc510c3","index":{"url":"z_b3d87d810e34754a_min_ann_py.html","file":"moptipyapps/dynamic_control/controllers/min_ann.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":473,"n_excluded":0,"n_missing":448,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_b3d87d810e34754a_partially_linear_py":{"hash":"ff7e35f7f79c8e150344d92fcc87d5c0","index":{"url":"z_b3d87d810e34754a_partially_linear_py.html","file":"moptipyapps/dynamic_control/controllers/partially_linear.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":94,"n_excluded":0,"n_missing":71,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_b3d87d810e34754a_peaks_py":{"hash":"477aac2b36d0ba7fb27ea514a9fc320a","index":{"url":"z_b3d87d810e34754a_peaks_py.html","file":"moptipyapps/dynamic_control/controllers/peaks.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":44,"n_excluded":0,"n_missing":19,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_b3d87d810e34754a_predefined_py":{"hash":"247388438cb8fd62ea7ac74f5ba92c91","index":{"url":"z_b3d87d810e34754a_predefined_py.html","file":"moptipyapps/dynamic_control/controllers/predefined.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":29,"n_excluded":0,"n_missing":12,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_b3d87d810e34754a_quadratic_py":{"hash":"dc9cedaa44e5acc4e3d48f0685a6f045","index":{"url":"z_b3d87d810e34754a_quadratic_py.html","file":"moptipyapps/dynamic_control/controllers/quadratic.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":25,"n_excluded":0,"n_missing":9,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f_experiment_raw_py":{"hash":"1000811bea66ae7c133f797aaa8a7252","index":{"url":"z_1200a9633299360f_experiment_raw_py.html","file":"moptipyapps/dynamic_control/experiment_raw.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":61,"n_excluded":0,"n_missing":20,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f_experiment_surrogate_py":{"hash":"ab5d12b89cf45db53c166a4b8ea97e98","index":{"url":"z_1200a9633299360f_experiment_surrogate_py.html","file":"moptipyapps/dynamic_control/experiment_surrogate.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":84,"n_excluded":0,"n_missing":47,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f_instance_py":{"hash":"8c9fc93f28952e31bc10d55153f6e9ec","index":{"url":"z_1200a9633299360f_instance_py.html","file":"moptipyapps/dynamic_control/instance.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":44,"n_excluded":0,"n_missing":10,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f_model_objective_py":{"hash":"93cbf63fa26989ce0e38f4bc4a685e68","index":{"url":"z_1200a9633299360f_model_objective_py.html","file":"moptipyapps/dynamic_control/model_objective.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":40,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f_objective_py":{"hash":"ebcddecf9967d4ea711acd08d2e685bc","index":{"url":"z_1200a9633299360f_objective_py.html","file":"moptipyapps/dynamic_control/objective.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":104,"n_excluded":0,"n_missing":6,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f_ode_py":{"hash":"e54db472d6b3de24194b561d235c44c1","index":{"url":"z_1200a9633299360f_ode_py.html","file":"moptipyapps/dynamic_control/ode.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":160,"n_excluded":0,"n_missing":40,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f_results_log_py":{"hash":"68497571badb4105c72988befbe3b32b","index":{"url":"z_1200a9633299360f_results_log_py.html","file":"moptipyapps/dynamic_control/results_log.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":46,"n_excluded":0,"n_missing":4,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f_results_plot_py":{"hash":"b3e00441fe805699e66dd9df7196929e","index":{"url":"z_1200a9633299360f_results_plot_py.html","file":"moptipyapps/dynamic_control/results_plot.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":159,"n_excluded":0,"n_missing":130,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f_starting_points_py":{"hash":"d8689b27b93f2ca6a5276211a3feaa9e","index":{"url":"z_1200a9633299360f_starting_points_py.html","file":"moptipyapps/dynamic_control/starting_points.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":76,"n_excluded":0,"n_missing":44,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f_surrogate_optimizer_py":{"hash":"1ab951f8bbfbe7b11b97d255f2920b03","index":{"url":"z_1200a9633299360f_surrogate_optimizer_py.html","file":"moptipyapps/dynamic_control/surrogate_optimizer.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":189,"n_excluded":0,"n_missing":43,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f_system_py":{"hash":"ce6843ef8b6e838e084de50e6ba787b1","index":{"url":"z_1200a9633299360f_system_py.html","file":"moptipyapps/dynamic_control/system.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":151,"n_excluded":0,"n_missing":84,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_1200a9633299360f_system_model_py":{"hash":"ddb5768a3b0353861241eb015998e6cf","index":{"url":"z_1200a9633299360f_system_model_py.html","file":"moptipyapps/dynamic_control/system_model.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":21,"n_excluded":0,"n_missing":3,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_c102750e44f8c672___init___py":{"hash":"552d5c849365ae586af6ecc6dd7de181","index":{"url":"z_c102750e44f8c672___init___py.html","file":"moptipyapps/dynamic_control/systems/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_c102750e44f8c672_lorenz_py":{"hash":"19c6e00613f1b455d73a52df558b46c5","index":{"url":"z_c102750e44f8c672_lorenz_py.html","file":"moptipyapps/dynamic_control/systems/lorenz.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":23,"n_excluded":0,"n_missing":6,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_c102750e44f8c672_stuart_landau_py":{"hash":"ba23da9e073cbfe46393db09dc334fc5","index":{"url":"z_c102750e44f8c672_stuart_landau_py.html","file":"moptipyapps/dynamic_control/systems/stuart_landau.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":21,"n_excluded":0,"n_missing":3,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_c102750e44f8c672_three_coupled_oscillators_py":{"hash":"8aac81377713c067ca19ea4ffaf6e50f","index":{"url":"z_c102750e44f8c672_three_coupled_oscillators_py.html","file":"moptipyapps/dynamic_control/systems/three_coupled_oscillators.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":48,"n_excluded":0,"n_missing":25,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_2bbffd00bd50e8dd___init___py":{"hash":"52c4823f09fe632bafdf075c09197c9e","index":{"url":"z_2bbffd00bd50e8dd___init___py.html","file":"moptipyapps/order1d/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_2bbffd00bd50e8dd_distances_py":{"hash":"f8c489662f4098de3144d5b47d5bb9fc","index":{"url":"z_2bbffd00bd50e8dd_distances_py.html","file":"moptipyapps/order1d/distances.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":19,"n_excluded":0,"n_missing":13,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_2bbffd00bd50e8dd_instance_py":{"hash":"573f70dcd8d1890a7a51bf46b8c6674d","index":{"url":"z_2bbffd00bd50e8dd_instance_py.html","file":"moptipyapps/order1d/instance.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":113,"n_excluded":0,"n_missing":19,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_2bbffd00bd50e8dd_space_py":{"hash":"5f69b111c1fb6dc5d4929c628fea55dc","index":{"url":"z_2bbffd00bd50e8dd_space_py.html","file":"moptipyapps/order1d/space.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":35,"n_excluded":0,"n_missing":1,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_f718b2200f202c7e___init___py":{"hash":"fde0f9ac82da21ddac347f8000c300c2","index":{"url":"z_f718b2200f202c7e___init___py.html","file":"moptipyapps/qap/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_f718b2200f202c7e_instance_py":{"hash":"766be252de44a3149f40d245f8753006","index":{"url":"z_f718b2200f202c7e_instance_py.html","file":"moptipyapps/qap/instance.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":131,"n_excluded":0,"n_missing":32,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_f718b2200f202c7e_objective_py":{"hash":"5ef19bb3e9c6890ab8686aaadd09d6ae","index":{"url":"z_f718b2200f202c7e_objective_py.html","file":"moptipyapps/qap/objective.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":33,"n_excluded":0,"n_missing":12,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_ef9ea0b050c2d384___init___py":{"hash":"1162024538861d05b3ab449a7dea835f","index":{"url":"z_ef9ea0b050c2d384___init___py.html","file":"moptipyapps/qap/qaplib/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":5,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_26fd35bf8bb8d301_shared_py":{"hash":"faafc054603610830e0d31b78652b6db","index":{"url":"z_26fd35bf8bb8d301_shared_py.html","file":"moptipyapps/shared.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":13,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_04f83d026ac37177___init___py":{"hash":"d0c41b445b86dfc44c2a0385318a163b","index":{"url":"z_04f83d026ac37177___init___py.html","file":"moptipyapps/tests/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_04f83d026ac37177_on_binpacking2d_py":{"hash":"ca9b84713101c31022e9748dfc45812a","index":{"url":"z_04f83d026ac37177_on_binpacking2d_py.html","file":"moptipyapps/tests/on_binpacking2d.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":154,"n_excluded":0,"n_missing":42,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_04f83d026ac37177_on_tsp_py":{"hash":"d6655b106c1fdf61e9cc56efa15dc519","index":{"url":"z_04f83d026ac37177_on_tsp_py.html","file":"moptipyapps/tests/on_tsp.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":98,"n_excluded":0,"n_missing":36,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_9a2d34382b20e3f7___init___py":{"hash":"0f576a64b44251b4fe466496eccc743d","index":{"url":"z_9a2d34382b20e3f7___init___py.html","file":"moptipyapps/tsp/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_9a2d34382b20e3f7_ea1p1_revn_py":{"hash":"6da736b3c31d4b313907d024f1a35171","index":{"url":"z_9a2d34382b20e3f7_ea1p1_revn_py.html","file":"moptipyapps/tsp/ea1p1_revn.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":57,"n_excluded":0,"n_missing":12,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_9a2d34382b20e3f7_fea1p1_revn_py":{"hash":"d10b0823b5080aaef5ea34f515a35f7d","index":{"url":"z_9a2d34382b20e3f7_fea1p1_revn_py.html","file":"moptipyapps/tsp/fea1p1_revn.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":69,"n_excluded":0,"n_missing":18,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_9a2d34382b20e3f7_instance_py":{"hash":"5396bac4e0bab33b4b2aad435bb4c4db","index":{"url":"z_9a2d34382b20e3f7_instance_py.html","file":"moptipyapps/tsp/instance.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":431,"n_excluded":0,"n_missing":71,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_9a2d34382b20e3f7_known_optima_py":{"hash":"3e094a7c7d063f1712126437d44314e1","index":{"url":"z_9a2d34382b20e3f7_known_optima_py.html","file":"moptipyapps/tsp/known_optima.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":48,"n_excluded":0,"n_missing":11,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_9a2d34382b20e3f7_tour_length_py":{"hash":"84f117923194286a6f3b1391588a98fc","index":{"url":"z_9a2d34382b20e3f7_tour_length_py.html","file":"moptipyapps/tsp/tour_length.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":33,"n_excluded":0,"n_missing":7,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_9fac6fe41d8874bc___init___py":{"hash":"cdc38412abac830b6fcb9c03cd0392fc","index":{"url":"z_9fac6fe41d8874bc___init___py.html","file":"moptipyapps/tsp/tsplib/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":5,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_2b9baf556b5321fb___init___py":{"hash":"9d0edc5c20a3b328954bfac6a103c05c","index":{"url":"z_2b9baf556b5321fb___init___py.html","file":"moptipyapps/ttp/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":0,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_2b9baf556b5321fb_errors_py":{"hash":"f02ef25f88f4812baa5190c57adeade3","index":{"url":"z_2b9baf556b5321fb_errors_py.html","file":"moptipyapps/ttp/errors.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":110,"n_excluded":0,"n_missing":93,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_2b9baf556b5321fb_game_encoding_py":{"hash":"8e76fb81b7678c30e711238f2d2ded9d","index":{"url":"z_2b9baf556b5321fb_game_encoding_py.html","file":"moptipyapps/ttp/game_encoding.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":48,"n_excluded":0,"n_missing":14,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_2b9baf556b5321fb_game_plan_py":{"hash":"64bc68a6ce71dd31b2d5c8b4ca517e56","index":{"url":"z_2b9baf556b5321fb_game_plan_py.html","file":"moptipyapps/ttp/game_plan.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":45,"n_excluded":0,"n_missing":3,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_2b9baf556b5321fb_game_plan_space_py":{"hash":"31c58f682252e72021deb3ecd67f05b5","index":{"url":"z_2b9baf556b5321fb_game_plan_space_py.html","file":"moptipyapps/ttp/game_plan_space.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":61,"n_excluded":0,"n_missing":10,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_2b9baf556b5321fb_instance_py":{"hash":"98d1ca4e3213c0b5f4a59fc123bb01d6","index":{"url":"z_2b9baf556b5321fb_instance_py.html","file":"moptipyapps/ttp/instance.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":202,"n_excluded":0,"n_missing":39,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_2b9baf556b5321fb_plan_length_py":{"hash":"39fe27728662950a279fdfe2f7c0112b","index":{"url":"z_2b9baf556b5321fb_plan_length_py.html","file":"moptipyapps/ttp/plan_length.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":52,"n_excluded":0,"n_missing":25,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_b1bc5d44f6786607___init___py":{"hash":"1c9d51290e364a73dbbd0f94bd8e88d1","index":{"url":"z_b1bc5d44f6786607___init___py.html","file":"moptipyapps/ttp/robinx/__init__.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":5,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}},"z_26fd35bf8bb8d301_version_py":{"hash":"71d80766f400b00425cb09516e0b0c5d","index":{"url":"z_26fd35bf8bb8d301_version_py.html","file":"moptipyapps/version.py","description":"","nums":{"precision":0,"n_files":1,"n_statements":2,"n_excluded":0,"n_missing":0,"n_branches":0,"n_partial_branches":0,"n_missing_branches":0}}}}} \ No newline at end of file